Git Product home page Git Product logo

xlocalizer's Introduction

XLocalizer for Asp.Net Core

Say bye-bye to manually creating localization resources...!

  • Online Translation: Auto translation of missed localized values.
  • Auto Key Adding: Auto adding missing keys to the resources files.
  • Multiple Resource Type Support: Built-in localization support based on .RESX, .XML, DB. Extendable localization support based on any custom file/db type.
  • Export to Resx: Resources from any source type can be exported to .RESX files via built-in exporters.
  • Do it Fast: Custom cache support for speeding up the process of getting localized values from sources.
  • Standard interfaces: Easy to use due to using the standard localization interfaces: IStringLocalizer, IHtmlLocalizer, IStringLocalizerFactory and IHtmlLocalizerFactory.

How it works:

XLocalizer Simplified Workflow

Setup

Install latest preview from nuget :

Install-Package XLocalizer

Add localization settings in startup.cs:

// Add XLocalizer
services.AddRazorPages()
    .AddXLocalizer<LocSource, GoogleTranslateService>(ops =>
    {
        ops.ResourcesPath = "LocalizationResources";
        ops.AutoTranslate = true;
        ops.AutoAddKeys = true;
        ops.TranslateFromCulture = "en";
    });

For more details goto DOCS.Ziyad.info

Step by step tutorial

Sample projects

License

MIT

xlocalizer's People

Contributors

lazziya avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

xlocalizer's Issues

issue installing package on core 3.1

HI I ran the command bellow and get the following error.
Install-Package XLocalizer

Install-Package : Unable to find package 'XLocalizer'
At line:1 char:1

  • Install-Package XLocalizer
  •   + CategoryInfo          : NotSpecified: (:) [Install-Package], Exception
      + FullyQualifiedErrorId : NuGetCmdletUnhandledException,NuGet.PackageManagement.PowerShellCmdlets.InstallPackageCommand
    

AmbiguousActionException when using services.AddMvc().AddXDbLocalizer<TDbContext, TTranslator>()

Hi Ziya,

Hope you're well.

I've successfully added XLocalizer.DB to a RazorPages project, but I'm having issues adding it to an existing Mvc project. Despite changes I've tried to make, I either get the 'AmbiguousActionException' exception or the route localization doesn't work at all. It's a very simple project with only one controller.

sdfsdfdsffff

Here's the Startup.cs:

public class Startup
    {
        ...

        public void ConfigureServices(IServiceCollection services)
        {
           ...

            var connection = Configuration.GetConnectionString("WLPDB");
            services.AddDbContext<PreferenceFormDbContext>(options =>
                options.UseSqlServer(connection),
                ServiceLifetime.Transient,
                ServiceLifetime.Transient);

            services.Configure<RequestLocalizationOptions>(ops =>
            {
                var supportedCultures = new[]
                {
                    new CultureInfo("cy"),
                    new CultureInfo("en")
                };

                ops.SupportedCultures = supportedCultures;
                ops.SupportedUICultures = supportedCultures;
                ops.DefaultRequestCulture = new RequestCulture("en");

                ops.RequestCultureProviders.Insert(0, new RouteSegmentRequestCultureProvider(supportedCultures));
            });

            services.AddMvc(
                options => {
                    options.EnableEndpointRouting = false;
                })
                .AddMvcOptions(ops => { ops.Conventions.Insert(0, new RouteTemplateModelConventionMvc()); })
                .AddXDbLocalizer<PreferenceFormDbContext, DummyTranslator>(ops =>
                {
                    ops.AutoAddKeys = false;
                    ops.AutoTranslate = false;
                })
                .AddRazorRuntimeCompilation();
            }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRequestLocalization();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }

And the Index method in the HomeController is very straight forward:

        [HttpGet]
        public IActionResult Index()
        {
            return View();
        }

I've got the exception to go away by removing:

    ops.RequestCultureProviders.Insert(0, new RouteSegmentRequestCultureProvider(supportedCultures));

and

    .AddMvcOptions(ops => { ops.Conventions.Insert(0, new RouteTemplateModelConventionMvc()); })

and updating my MapRoute to:

    routes.MapRoute(
        name: "default",
        template: "{culture=en}/{controller=Home}/{action=Index}/{id?}");

But doing this ignores the culture in the URL--it always returns "en", even if "cy" is in the URL.

Any guidance would be really appreciated. I'm more than likely doing something silly again.

Cheers
Morgan

FluentValidation placeholders with XLocalizer

Hi Ziya

Hope you had a nice weekend.

I stumbled onto something and couldn't figure out a way around it. I'm using FluentValidation for my validating my ViewModels. It supports the ability to use placeholders. This works as expected using the following code (not using XLocalizer):

    public class SearchViewModelValidation : AbstractValidator<SearchViewModel>
    {
        public SearchViewModelValidation()
        {
            RuleFor(svm => svm.SearchQuery)
                .MaximumLength(4000)
                .WithMessage("Your query must be less than {MaxLength} characters");
        }
    }

In my View, I'd see an error message like so: Your query must be less than 4000 characters should the user trigger it.

However, if I store the string in my database like so:

sdklfjkldsjfdff

Using XLocalizer v1.0.0, I can retrieve the string like this:

    public class SearchViewModelValidation : AbstractValidator<SearchViewModel>
    {
        public SearchViewModelValidation(ILocalizationService loc)
        {
            RuleFor(svm => svm.SearchQuery)
                .MaximumLength(4000)
                .WithMessage(loc.GetLocalizedString("DIRECTORYSEARCH_VALIDATION_MAXLENGTH"));
        }
    }

But unfortunately, the following exception is thrown:

sksssssssssssss

I have appended the whole stack trace to the end of this post. My theory is XLocalizer is expecting a number to be in-between the { and }. Is there a way of accommodating the placeholders FluentValidation supports and the ones XLocalizer provides?

Thanks, as always!
Morgan

System.FormatException: Input string was not in a correct format.
   at System.Text.StringBuilder.FormatError()
   at System.Text.StringBuilder.AppendFormatHelper(IFormatProvider provider, String format, ParamsArray args)
   at System.String.FormatHelper(IFormatProvider provider, String format, ParamsArray args)
   at System.String.Format(String format, Object[] args)
   at XLocalizer.DB.DbStringLocalizer`1.GetLocalizedString(String name, Object[] arguments)
   at XLocalizer.DB.DbStringLocalizer`1.get_Item(String name)
   at Microsoft.Extensions.Localization.StringLocalizerExtensions.GetString(IStringLocalizer stringLocalizer, String name)
   at WLP.Infrastructure.Localization.Services.LocalizationService.GetString(String key) in C:\Web Server\Welsh Language Preference\WLP.Infrastructure\Localization\Services\LocalizationService.cs:line 32
   at WLP.Infrastructure.Localization.Services.LocalizationService.GetLocalizedString(String key) in C:\Web Server\Welsh Language Preference\WLP.Infrastructure\Localization\Services\LocalizationService.cs:line 27
   at WLP.Application.Validation.SearchViewModelValidation..ctor(ILocalizationService loc) in C:\Web Server\Welsh Language Preference\WLP.Application\Validation\SearchViewModelValidation.cs:line 11
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
   at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
   at FluentValidation.AspNetCore.ServiceProviderValidatorFactory.CreateInstance(Type validatorType) in /home/jskinner/code/FluentValidation/src/FluentValidation.AspNetCore/ServiceProviderValidatorFactory.cs:line 32
   at FluentValidation.ValidatorFactoryBase.GetValidator(Type type) in /home/jskinner/code/FluentValidation/src/FluentValidation/ValidatorFactoryBase.cs:line 41
   at FluentValidation.AspNetCore.ValidatorDescriptorCache.GetCachedDescriptor(ClientValidatorProviderContext context, IHttpContextAccessor httpContextAccessor) in /home/jskinner/code/FluentValidation/src/FluentValidation.AspNetCore/ValidatorDescriptorCache.cs:line 51
   at FluentValidation.AspNetCore.FluentValidationClientModelValidatorProvider.CreateValidators(ClientValidatorProviderContext context) in /home/jskinner/code/FluentValidation/src/FluentValidation.AspNetCore/FluentValidationClientModelValidatorProvider.cs:line 73
   at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.CompositeClientModelValidatorProvider.CreateValidators(ClientValidatorProviderContext context)
   at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ClientValidatorCache.GetValidators(ModelMetadata metadata, IClientModelValidatorProvider validatorProvider)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.DefaultValidationHtmlAttributeProvider.AddValidationAttributes(ViewContext viewContext, ModelExplorer modelExplorer, IDictionary`2 attributes)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ValidationHtmlAttributeProvider.AddAndTrackValidationAttributes(ViewContext viewContext, ModelExplorer modelExplorer, String expression, IDictionary`2 attributes)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.DefaultHtmlGenerator.AddValidationAttributes(ViewContext viewContext, TagBuilder tagBuilder, ModelExplorer modelExplorer, String expression)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.DefaultHtmlGenerator.GenerateInput(ViewContext viewContext, InputType inputType, ModelExplorer modelExplorer, String expression, Object value, Boolean useViewData, Boolean isChecked, Boolean setId, Boolean isExplicitValue, String format, IDictionary`2 htmlAttributes)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.DefaultHtmlGenerator.GenerateTextBox(ViewContext viewContext, ModelExplorer modelExplorer, String expression, Object value, String format, Object htmlAttributes)
   at Microsoft.AspNetCore.Mvc.TagHelpers.InputTagHelper.GenerateTextBox(ModelExplorer modelExplorer, String inputTypeHint, String inputType, IDictionary`2 htmlAttributes)
   at Microsoft.AspNetCore.Mvc.TagHelpers.InputTagHelper.Process(TagHelperContext context, TagHelperOutput output)
   at Microsoft.AspNetCore.Razor.TagHelpers.TagHelper.ProcessAsync(TagHelperContext context, TagHelperOutput output)
   at Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner.RunAsync(TagHelperExecutionContext executionContext)
   at AspNetCore.Views_Shared_Partials__Search.<ExecuteAsync>b__28_0() in C:\Web Server\Welsh Language Preference\WLP.Directory.WebUI\Views\Shared\Partials\_Search.cshtml:line 6
   at Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext.GetChildContentAsync(Boolean useCachedResult, HtmlEncoder encoder)
   at Microsoft.AspNetCore.Mvc.TagHelpers.RenderAtEndOfFormTagHelper.ProcessAsync(TagHelperContext context, TagHelperOutput output)
   at Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner.<RunAsync>g__Awaited|0_0(Task task, TagHelperExecutionContext executionContext, Int32 i, Int32 count)
   at AspNetCore.Views_Shared_Partials__Search.ExecuteAsync() in C:\Web Server\Welsh Language Preference\WLP.Directory.WebUI\Views\Shared\Partials\_Search.cshtml:line 3
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, Boolean invokeViewStarts)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.HtmlHelper.RenderPartialCoreAsync(String partialViewName, Object model, ViewDataDictionary viewData, TextWriter writer)
   at AspNetCore.Views_Home_Index.ExecuteAsync() in C:\Web Server\Welsh Language Preference\WLP.Directory.WebUI\Views\Home\Index.cshtml:line 7
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, Boolean invokeViewStarts)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, String contentType, Nullable`1 statusCode)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, String contentType, Nullable`1 statusCode)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ActionContext actionContext, IView view, ViewDataDictionary viewData, ITempDataDictionary tempData, String contentType, Nullable`1 statusCode)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor.ExecuteAsync(ActionContext context, ViewResult result)
   at Microsoft.AspNetCore.Mvc.ViewResult.ExecuteResultAsync(ActionContext context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|29_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Localization.RequestLocalizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.StatusCodePagesMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

? Only data annotations translation

Hi,
how can I have only data annotations translated? I want to disable culture routing but I'm unable to do it.
Data annotations translation is working perfectly however.
Also is there way to add custom AccessDenied page?

I see entry in

  <data name="Access denied" xml:space="preserve">
    <value>Pristup zabranjen</value>
    <comment>Areas/Identity/Pages/Account/AccessDenied.cshtml</comment>
  </data>

I've added custom page in
Areas/Identity/Pages/Account/AccessDenied.cshtml
but it's not being loaded.

After updating to 1.0.2, XML approach could not get value for neutral language (en culture).

Hi @LazZiya ,

Hope you are doing well,

This issue happened after I updated the localizer version to 1.0.2.
I'm using the XML approach with autoKey and translate options.

Before the translation work perfect for both languages (Arabic, English).

LocSource.ar.xml
image

LocSource.en.xml
image

And the value was getting correctly.
After updating, only the key name is getting with 'en' culture, the Arabic is working fine.

English Culture
image
Arabic Culture
image

I returned to the previous version until the issue is fixed.
I hope this issue is clear to you. And Many Thanks to you.

AmbiguousMatchException: The request matched multiple endpoints

Originally posted by @Malkawi1 in #2 (comment)

hi sir,

how to edit this code ->

// add culture route segment for controllers e.g. /en/Home/Index
    .AddMvcOptions(ops => ops.Conventions.Insert(0, new RouteTemplateModelConventionMvc()))

because make this error:

 An unhandled exception occurred while processing the request.
AmbiguousMatchException: The request matched multiple endpoints. Matches:

Microsoft.AspNetCore.OData.Routing.Controllers.MetadataController.GetServiceDocument (Microsoft.AspNetCore.OData)
Microsoft.AspNetCore.OData.Routing.Controllers.MetadataController.GetMetadata (Microsoft.AspNetCore.OData)
Link.Controllers.GroupsController.ConfirmEmail (Link)
Link.Controllers.HomeController.Error (Link)
Link.Controllers.HomeController.ChatBot (Link)
Link.Controllers.HomeController.Index (Link)
Link.Controllers.GroupsController.SendRequest (Link)
Link.Controllers.CommentsController.Index (Link)
Link.Controllers.GroupsController.Create (Link)
Link.Controllers.GroupsController.Details (Link)
Link.Controllers.GroupsController.Groups (Link)
Link.Controllers.GroupsController.Index (Link)
Link.Controllers.CommentsController.Hashtag (Link)
Link.Areas.Admin.Controllers.HomeController.Index (Link)
Link.Controllers.GroupsController.Edit (Link)
Link.Controllers.PeopleController.Index (Link)

Microsoft.AspNetCore.Routing.Matching.DefaultEndpointSelector.ReportAmbiguity(CandidateState[] candidateState)


_Originally posted by @Malkawi1 in https://github.com/LazZiya/XLocalizer/issues/2#issuecomment-1328900840_
      

Configure XLocalizer to use XML files inside an RCL

Hi Ziya

Hope you've been well.

I'm trying to use XLocalizer with the XML provider (XLocalizer.Xml.XmlResourceProvider), but I'm having an issue with getting the resources from the XML files at runtime.

I have two WebUi projects that need to share resources, so I have a CommonUi project (an RCL) to put them in. However, at runtime, these XML files are ignored and new ones are created in the startup project. Note in the first screen shot that there's no 'Localization' folder inside the ManagerUi project:

image

However, after I run it, this folder is created:

image

How do I tell XLocalizer to use the resource keys inside CommonUi/Resources/LocalizationResources/*.x.xml instead of ManagerUi/Resources/LocalizationResources/*.x.xml?

In case it helps. Here's how I've configured XLocalizer:

services.Configure<RequestLocalizationOptions>(opts =>
{
    var cultures = new CultureInfo[]
    {
        new CultureInfo("en")
        , new CultureInfo("cy")
    };

    opts.SupportedCultures = cultures;
    opts.SupportedUICultures = cultures;
    opts.DefaultRequestCulture = new RequestCulture("en");
    opts.RequestCultureProviders.Insert(0, new RouteSegmentRequestCultureProvider(cultures));
});

services.AddSingleton<IXResourceProvider, XmlResourceProvider>();
builder.AddRazorPagesOptions((ops) =>
{
    ops.Conventions.Insert(0, new RouteTemplateModelConventionRazorPages());
})
    .AddXLocalizer<Caforb.CommonUi.Localization.LocalizationResources.LocSource, DummyTranslator>((opts) =>
    {
        opts.ResourcesPath = $"Localization/LocalizationResources";

        opts.AutoTranslate = false;
        opts.AutoAddKeys = false;

        //opts.UseExpressMemoryCache = true;
        opts.UseExpressMemoryCache = false;

        opts.TranslateFromCulture = "en";
        opts.LocalizeDefaultCulture = true;

        ...
    });

Kind regards
Morgan

XLocalizer.DB database concurrency problem

Hi Ziya

Hope you're well.

I've been having intermittent issues for the past couple of months with EF complaining about two instances of a DbContext being used at the same time. I recently looked at the logs to try and figure out what's going on and it seems in all cases XLocalizer.DB is involved. The exception I'm referring to is:

System.InvalidOperationException: A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext.

I may be misinterpreting the stack trace, but the exception seems to occur within:

XLocalizer.DB.EF.EFDbResourceProvider.TryGetValue<TResource>(string, string)

every time I see it in my logs.

I've attached a couple of stack traces in case that helps. It's worth noting that this doesn't happen all the time. It's hard to reproduce, but it seems to happen when I excessively refresh a page that uses the localize-content tag helper somewhere on it.

If I can provide anymore information or provide additional stack traces, please let me know.

Thanks
Morgan

In application 1:

  • XLocalizer.TagHelpers 1.1.0
  • XLocalizer 1.0.3
  • XLocalizer.DB 1.0.3

st-1.txt
st-2.txt
st-3.txt
st-4.txt
st-5.txt
st-6.txt

In application 2:

  • XLocalizer.TagHelpers 1.0.0
  • XLocalizer 1.0.0
  • XLocalizer.DB 1.0.0

other-app-st-1.txt

How to set default culture?

Hello,

I am wondering how can I set a default culture for all routes if culture value isn't provided in the URL!
As you can see here the Identity razor page is working well without explicitly specifying the culture value.
image

Whereas in a view like this (controller - view) the page isn't working unless I explicitly provide a culture in the URL.
image

Thank you!!!

Similar keys that are stored in different resources caching the same value

ExpressMemoryCache is caching key-value pairs in memory for fast access. But when we have similar keys with different translations that are stored in different resource files only one value is stored in memory.

e.g.:
Suppose we have below localization resources:

  • AdminResources.resx
  • UserResources.Resx

And we have the below key-value pairs in both resources:

  • "Welcome": "Welcome admin"
  • "Welcome": "Welcome user"

ExpressMemoryCache will store only first key-value, and when IStringLocalizer is called for the second resource, it will retrive the value from cache that is stored for the first resource.

IStringLocalizer<AdminResource> _loc1;
IStringLocalizer<AdminResource> _loc2;

// UseExpressMemoryCache = true
_loc1["Welcome"] // output: Welcome admin
_loc2["Welcome"] // output: Welcome admin

// UseExpressMemoryCache = false
_loc1["Welcome"] // output: Welcome admin
_loc2["Welcome"] // output: Welcome user

fail: XLocalizer.Resx.ResxResourceProvider[0]

Hi,

First, congratulations for XLocalizer ๐Ÿ˜ƒ
It's really a nice Library and finally I found a solution to translate .NET 6 Data Annotation errors.
Almost unbelievable Data Annotations translation is not available in .NET 6 as it was in .NET Framework.

I created a sample project and it works fine ( ASP.NET does not show any error ), BUT, watching Kestrel messages the following "fail" is shown:

fail: XLocalizer.Resx.ResxResourceProvider[0]
Could not find the resource "Northwind.Mvc.XLocalizer.XLocalizerSource.resources"
among the resources "" embedded in the assembly "Northwind.Mvc",
nor among the resources in any satellite assemblies for the specified culture.
Perhaps the resources were embedded with an incorrect name.

I will not use Resource Files, but trying to remove this "fail" I created one: but the "fail" is still there.
I will not use Translation too: I just translated by myself the Data Annotation errors to pt-BR.
Below you find the current configuration and the files I created.
Both defining in JSON ( appsettings.json ) or defining in CODE ( Startup.cs ) works: the only problem is the "fail" message.
What am I doing wrong ?

Thanks

\XLocalizer
    XLocalizerSource.cs ( namespace Northwind.Mvc.XLocalizer )
    XLocalizerSource.en-US.cs
    XLocalizerSource.pt-BR.cs  

Startup.cs

IMvcBuilder mvcBuilder = services
    .AddControllersWithViews(o => o.EnableEndpointRouting = false)
    .AddNewtonsoftJson(o =>
    {
        o.SerializerSettings.ContractResolver = new DefaultContractResolver();
        o.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
    })
    // IStringLocalizer
    //.AddDataAnnotationsLocalization()
    //.AddDataAnnotationsLocalization(o =>
    //{
    //    o.DataAnnotationLocalizerProvider = (type, factory) =>
    //        o.Create(typeof(SharedResource));
    //})
    //.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix);
    .AddViewOptions(o =>
    {
        o.HtmlHelperOptions.ClientValidationEnabled = true;
    })
    // XLocalizer
    // JSON
    //.AddXLocalizer<XLocalizerSource>(o => configuration.GetSection("XLocalizerOptions").Bind(o));
    // C#
    .AddXLocalizer<XLocalizerSource>(o =>
    {
        o.AutoAddKeys = false;
        o.AutoTranslate = false;
        o.ResourcesPath = "XLocalizer";
        o.TranslateFromCulture = "pt-BR";
        o.UseExpressMemoryCache = true;
        o.IdentityErrors = new IdentityErrors 
        {
            DuplicateEmail = "O e-mail '{0}' jรก foi levado.",

localize-att-* also localize tag's content

Hi Ziya

Hope you're well.

Whenever I use the localize-att- tag helper, not only does it localize the attribute's value, but the tag's contents as well. For example:

<p localize-att-title="foo">bar</p>

Returns this in the console:

warn: XLocalizer.DB.EF.EFDbResourceProvider[0]
      Resource not exist! Culture: 'cy', Key: 'foo'
warn: XLocalizer.DB.EF.EFDbResourceProvider[0]
      Resource not exist! Culture: 'cy', Key: 'bar'

I'd expect it to log:

warn: XLocalizer.DB.EF.EFDbResourceProvider[0]
      Resource not exist! Culture: 'cy', Key: 'foo'

I'm using the localize-att-* extensively in my project and I'd like to reduce the amount of redundant database traffic. A real world example would be localizing Bootstrap's "close" label. I only want to localize the aria-label attribute, not the content of the tag (as it's just an icon).

<button type="button" class="close" data-dismiss="modal" localize-att-aria-label="Close">
    <span aria-hidden="true">&times;</span>
</button>

I expect only one request to fetch the resource with the key "Close", but instead I get two. One for "Close" and another for "<span aria-hidden="true">&times;</span>".

Is this working as intended? If so, would it be possible to add an option to only localize the tag's contents when localize-content is added to the tag please?

Package versions:

<PackageReference Include="XLocalizer" Version="1.0.3" />
<PackageReference Include="XLocalizer.DB" Version="1.0.3" />
<PackageReference Include="XLocalizer.TagHelpers" Version="1.1.0" />

Cheers
Morgan

Set Arabic as default request culture

Hello,

I am trying to make the primary language(Default request): Arabic, The code in ConfigureServices is the following:

    string defCulture = configuration.GetSection("Defaults").GetSection("DefalutLang").Value.Trim();
            var cultures = CultureHelper.GetArrOfCultureInfo(configuration);


            
            services.Configure<RequestLocalizationOptions>(ops =>
            {
                ops.DefaultRequestCulture = new RequestCulture(defCulture);
                ops.SupportedCultures = cultures;
                ops.SupportedUICultures = cultures;
                
                ops.RequestCultureProviders.Clear();
                ops.RequestCultureProviders.Insert(0, new RouteSegmentRequestCultureProvider(cultures));

            });

    services.AddControllersWithViews()
             .AddRazorRuntimeCompilation()
             .AddMvcOptions(ops => ops.Conventions.Insert(0, new RouteTemplateModelConventionMvc()))
             .AddRazorPagesOptions(ops => ops.Conventions.Insert(0, new RouteTemplateModelConventionRazorPages()))
             .AddXLocalizer<LocSource>(ops =>
             {
                 ops.ResourcesPath = "LocalizationResources";
             });

            services.AddMvc().AddNewtonsoftJson(options =>
            {
                options.SerializerSettings.ContractResolver = new DefaultContractResolver();
            });

The defCulture variable is: ar
and the cultures variable is an array of two values {'ar','en'}

In the Configure method:

app.UseResponseCompression();
            app.UseHttpsRedirection();
            app.UseStaticFiles(new StaticFileOptions
            {
                ServeUnknownFileTypes = false,
                OnPrepareResponse = ctx =>
                {
                    const int durationInSeconds = 60 * 60 * 24;
                    ctx.Context.Response.Headers[HeaderNames.CacheControl] = "public,max-age=" + durationInSeconds;
                    ctx.Context.Response.Headers[HeaderNames.Expires] = new[] { DateTime.UtcNow.AddYears(1).ToString("R") }; // Format RFC1123
                }
            });
            app.UseCookiePolicy();
            app.UseRouting();
            app.UseAuthentication();
            app.UseAuthorization();
            app.UseSession();
            app.UseRequestLocalization();



//Endpoint
 string defCulture = configuration.GetSection("Defaults").GetSection("DefalutLang").Value.Trim();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                                     name: "default",
                                     pattern: "{culture=" + defCulture + "}/{controller=Home}/{action=Index}/{id?}",
                                     defaults: new { culture = defCulture, controller = "Home", action = "Index" });

                endpoints.MapRazorPages();
            });

In the view i have the following:

@inject IOptions<RequestLocalizationOptions> LocOps

@{

    var requestCulture = CultureInfo.CurrentCulture;

When debugging the code with breakpoint the variable requestCulture value is : ar

but the issue is with the values read from resx file, such as:
@_loc.GetString("Search")
the search name and value exist in the \LocalizationResources folder within LocSource.ar.resx file.

the value Search(as an example) is still in English, it's not translated into Arabic.!!?

ISharedCultureLocalizer moved/missing?

Hi Ziya,

I'm really hoping this isn't a case of adding a using statement for a Microsoft namespace again!

I'm migrating some of my services from using LazZiya.ExpressLocalization to XLocalizer. One of the tools I used heavily was the ISharedCultureLocalizer (ISharedCultureLocalizer.GetLocalizedString(string) and ISharedCultureLocalizer.GetLocalizedString(Type, string) specificaly). I can't seem to find it in XLocalizer nor can I figure out how to call those methods anymore.

Has it been moved or renamed? I used to use it like this:

`
using LazZiya.ExpressLocalization;

public class LocalizationService : ILocalizationService
{
private readonly ISharedCultureLocalizer _loc;

    public LocalizationService(ISharedCultureLocalizer loc)
    {
        _loc = loc;
    }

    public TEntity Localize<TEntity>(string key)
    {
            ...
            return _loc.GetLocalizedString(key);
    }

}
`

As always, thank you very much.

Morgan

Adding option to refresh cache memory

Hi Ziya,

Again me ;)

This time not with a bug but rather a suggestion for the useful feature.

It would be useful that there is the possibility to call a method to update/refresh cache values.

Does maybe this kind of function already exist?
Is it maybe possible to get to cache object over dependency injection or something similar?
If the function doesn't exist maybe u can point me in the right direction and I will be more than happy to make a pull request.

Best,
Vito

ExDataAnnotations with a DefaultRequestCulture different from "en"

Hi!
First of all, I want to tell you that what you are doing here is a hell of a job and I love it.

Well, to the point:
I have seen that if the "DefaultRequestCulture" is different from "en", the texts of the "ExDataAnnotations" are not entered in the default language file, so the default English text is used.
If we then enter this text in the xml by hand, it works correctly.

My suggestion is that, if the texts that come from the Framewok can be identified in some way and the "DefaultRequestCulture" is different from "en", they should always be entered and translated in their xml file, and if we cannot identify them, then all the texts should be copied (but not translated), so it will be easier to locate these texts to do it manually.

However, the latter should always be a configurable option, so that if it is active, all texts are always available to be changed without having to redeploy the application again if the default language texts have to be changed.

What do you think about all this?

Multi-tenancy provider

Hello,

My question is about some guidance to see whether I can change your code so I can try to modify your code to support multi-tenancy. Let me brief my setup. I am trying to keep it simple and only considering storing strings (no localized icons, images etc.)

Each tenant has it's own database and each tenant can customize their resource strings. So your table structure can be reused

Tenant identification is happening via the url pattern. example :- tenant1.domain.com , tenant2.domain.com. So the app will identitify tenant1/tenant2 and then load the correct connection string to work with the correct database for that tenant. For multitenancy i am using https://github.com/Finbuckle/Finbuckle.MultiTenant. 

So our localization setup should be able to load resources from the connection string based on the tenant that is logged in (identified via subdomain).

What I don't get is which parts in your code needs customizations to use the connection string dynamically. Basically resource should be loaded at the first request which is the only way to identify the tenant. So itll be the point whre it loads all resources fro that tenant and cache it for subsequent calls for that tenant. Ex:- a dictionary object with <key,resource collection> like <tenant1,tenant1resourcecollection>

If you can give me some pointers, I will try to manage it myself.

InvalidOperationException: Cache entry must specify a value for Size when SizeLimit is set.

Application: asp.net core 5 web application
Razor Page Crashed

<a class="materials-filter-item @allActive" asp-area="" asp-route-interioreffectid="@Model.interioreffectid" asp-page="/Interiors">
    <localize>All</localize>
    @if (!String.IsNullOrEmpty(allActive))
    {
        <svg data-src="/Content/images/arrow-right-down.svg" fill="transparent"></svg>
    }
</a>

Exception

Microsoft.Extensions.Caching.Memory.MemoryCache.SetEntry(CacheEntry entry)
Microsoft.Extensions.Caching.Memory.CacheEntry.Dispose()
Microsoft.Extensions.Caching.Memory.CacheExtensions.Set<TItem>(IMemoryCache cache, object key, TItem value, MemoryCacheEntryOptions options)
XLocalizer.Common.ExpressMemoryCache.Set<T>(string key, string value)
XLocalizer.XStringLocalizer<TResource>.GetLocalizedString(string name, object[] arguments)
XLocalizer.XStringLocalizer<TResource>.get_Item(string name, object[] arguments)
XLocalizer.XHtmlLocalizer.GetHtmlString(string name, object[] arguments)
XLocalizer.XHtmlLocalizer.get_Item(string name)
XLocalizer.TagHelpers.LocalizationTagHelperBase.GetLocalizedHtmlString(string str)
XLocalizer.TagHelpers.LocalizationTagHelperBase.ProcessAsync(TagHelperContext context, TagHelperOutput output)
XLocalizer.TagHelpers.LocalizeTagHelper.ProcessAsync(TagHelperContext context, TagHelperOutput output)
Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner.<RunAsync>g__Awaited|0_0(Task task, TagHelperExecutionContext executionContext, int i, int count)
Pratta.Pages.Pages_Interiors+<>c__DisplayClass15_1+<<ExecuteAsync>b__2>d.MoveNext()
Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext.SetOutputContentAsync()
Pratta.Pages.Pages_Interiors.ExecuteAsync() in Interiors.cshtml
+
                    <a class="materials-filter-item @allActive" asp-area="" asp-route-interioreffectid="@Model.interioreffectid" asp-page="/Interiors">
Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context)
Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, bool invokeViewStarts)
Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context)
Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, string contentType, Nullable<int> statusCode)
Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, string contentType, Nullable<int> statusCode)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|29_0<TFilter, TFilterAsync>(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext<TFilter, TFilterAsync>(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
Microsoft.AspNetCore.Localization.RequestLocalizationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

XLocalizer.Resx.ResxResourceProvider

I am getting an error in the event viewer. every time I switch from English to Arabic

Category: XLocalizer.Resx.ResxResourceProvider
EventId: 0
SpanId: 0045c129f6cb0af8
TraceId: 8363e0583dfeb1894b13492b134b631c
ParentId: 0000000000000000
RequestId: 8000457b-0000-ed00-b63f-84710c7967bb
RequestPath: /ar/Aboutus
ActionId: 3f0a219e-baf9-426f-909d-f0ae59dd2874
ActionName: MP.Web.Controllers.HomeController.AboutUs (MP.Web)

Could not find the resource "MP.Web.LocalizationResources.LocSource.resources" among the resources "" embedded in the assembly "MP.Web", nor among the resources in any satellite assemblies for the specified culture. Perhaps the resources were embedded with an incorrect name.

Do you have any idea what it's about.?

localize-att- in Hyperlinks has forced localize-content

<nav class="justify-content-center pagenationNav" aria-label="Page navigation"> <ul class="pagination"> <li class="page-item pagenationFirstLI"> <a asp-route-pageIndex="1" asp-route-sortOrder="@Model.CurrentSort" asp-route-searchString="@Model.CurrentFilter" asp-route-pageSize="@Model.PageSize" aria-label="First" localize-att-title="First" class="page-link pagenationFirst @prevDisabled"> <span aria-hidden="true">&laquo;</span> </a> </li> </ul> </nav>

localize-att-title="First"
warning message: Resource not exist! Culture: 'ar', Key: '< span aria-hidden="true">ยซ< /span>'

Resource selection order

First of all: thank you very much for the work you are doing, it is impressive.

I have encountered a couple of problems. To do this, I put my configuration:
ASP NET Core 3.1

     services.Configure<RequestLocalizationOptions>(options =>
      {
        var supportedCultures = new[]
        {
          new CultureInfo("es-ES"),
          new CultureInfo("en-US")
        };
        options.DefaultRequestCulture = new RequestCulture(culture: "es-ES", uiCulture: "es-ES");
        options.SupportedCultures = supportedCultures;
        options.SupportedUICultures = supportedCultures;

        options.RequestCultureProviders.Insert(0, new RouteSegmentRequestCultureProvider(supportedCultures));
        options.RequestCultureProviders.Insert(1, new CookieRequestCultureProvider() { CookieName = "_KCul" });
      });

      services
        .AddRazorPages(options =>
        {
          options.RootDirectory = "/Pages";
          options.Conventions.AllowAnonymousToPage("/About");
          options.Conventions.AllowAnonymousToPage("/Contact");
          options.Conventions.AllowAnonymousToPage("/Error");
          options.Conventions.AllowAnonymousToPage("/Index");
          options.Conventions.AllowAnonymousToPage("/Privacy");
          options.Conventions.AllowAnonymousToPage("/StatusCode");
          options.Conventions.AllowAnonymousToPage("/Export");
          options.Conventions.AuthorizeAreaFolder("Identity", "/Account/Manage");

          options.Conventions.Insert(0, new RouteTemplateModelConventionRazorPages());
        })
        .AddXLocalizer<ViewLocalizationResource>(ops =>
        {
          ops.ResourcesPath = "LocalizationResources";
          ops.AutoTranslate = false;
          ops.AutoAddKeys = false;
          ops.UseExpressMemoryCache = !Development;
        })
        .AddXLocalizer<ViewLocalizationResource, MyMemoryTranslateService>(ops =>
        {
          ops.ResourcesPath = "LocalizationResources";
          ops.AutoTranslate = true;
          ops.AutoAddKeys = true;
          ops.UseExpressMemoryCache = !Development;
        });

      services.AddSingleton<IModelBindingErrorMessagesProvider, SpanishModelBindingErrors>();


      // Register the built-in Xml resource provider
      services.AddSingleton<IXResourceProvider, ResxResourceProvider>();
      services.AddTransient<IXResourceExporter, XmlResourceExporter>();
      services.AddSingleton<IXResourceProvider, XmlResourceProvider>();
      services.AddHttpClient<ITranslator, MyMemoryTranslateService>();`

1st In Development, debugging with Visual Studio, it never uses the resources that exist in the "* .resx" file.
If it exists in the "* .xml", use it. If it doesn't exist, use "MyMemoryTranslateService" and add it to the "* .xml"
But it never looks for the resource in the "* .resx"
2nd In production, never use the existing resources in the files "* .resx" or "* .xml", use "MyMemoryTranslateService".
If the resource exists in "* .resx" or in "* .xml", it does not use them, but calls "MyMemoryTranslateService" and stores it in "ExpressMemoryCache".

How can the behavior be changed so that the search order of the resources is:
1st "ExpressMemoryCache"
2nd "* .resx"
3rd "* .xml"
4th "MyMemoryTranslateService"

I am sorry for my bad english. I use Google Translator

Thank you very much for your help

This is an update:

I have changed the order of the instructions and now it looks more like what I want:

` services.AddHttpClient<ITranslator, MyMemoryTranslateService>();

  services.Configure<RequestLocalizationOptions>(options =>
  {
    var supportedCultures = new[]
    {
      new CultureInfo("es-ES"),
      new CultureInfo("en-US")
    };
    options.DefaultRequestCulture = new RequestCulture(culture: "es-ES", uiCulture: "es-ES");
    options.SupportedCultures = supportedCultures;
    options.SupportedUICultures = supportedCultures;

    options.RequestCultureProviders.Insert(0, new RouteSegmentRequestCultureProvider(supportedCultures));
    options.RequestCultureProviders.Insert(1, new CookieRequestCultureProvider() { CookieName = "_KCul" });
  });

  services.AddLocalization(o => o.ResourcesPath = "LocalizationResources");

  // Register the built-in Xml resource provider
  services.AddSingleton<IXResourceProvider, XmlResourceProvider>();
  services.AddSingleton<IXResourceProvider, ResxResourceProvider>();
  services.AddTransient<IXResourceExporter, XmlResourceExporter>();

  services
    .AddRazorPages(options =>
    {
      options.RootDirectory = "/Pages";
      options.Conventions.AllowAnonymousToPage("/About");
      options.Conventions.AllowAnonymousToPage("/Contact");
      options.Conventions.AllowAnonymousToPage("/Error");
      options.Conventions.AllowAnonymousToPage("/Index");
      options.Conventions.AllowAnonymousToPage("/Privacy");
      options.Conventions.AllowAnonymousToPage("/StatusCode");
      options.Conventions.AllowAnonymousToPage("/Export");
      options.Conventions.AuthorizeAreaFolder("Identity", "/Account/Manage");

      options.Conventions.Insert(0, new RouteTemplateModelConventionRazorPages());
    })
    .AddXLocalizer<ViewLocalizationResource>(ops =>
    {
      ops.ResourcesPath = "LocalizationResources";
      ops.AutoTranslate = false;
      ops.AutoAddKeys = false;
      ops.UseExpressMemoryCache = !Development;
    })
    .AddXLocalizer<ViewLocalizationResource, MyMemoryTranslateService>(ops =>
    {
      ops.ResourcesPath = "LocalizationResources";
      ops.AutoTranslate = true;
      ops.AutoAddKeys = true;
      ops.UseExpressMemoryCache = !Development;
    });

  services.AddSingleton<IModelBindingErrorMessagesProvider, SpanishModelBindingErrors>();`

In this order, check first if the resource exists in the "* .resx" files. However, you cannot add new keys as you are using "ResxResourceProvider".

In other words, only one resource provider is used, the last one to register. So "XmlResourceProvider" doesn't work, so no new keys can be added.

Is it possible to localize <select asp-items />?

Hi Ziya,

Hope you're well.

Is it possible to localize the contents of a <select asp-items /> automatically?

For example, in a View:

@{
    var items = new List<SelectListItem>();
    items.Add(new SelectListItem
    {
        Text = "Some string to be localized", 
        Value = "SomeValue" 
    });
}
...
<select asp-items="@items"></select>
...

I'm not sure where I'd put the localize-content or localize-att-* to tell it to do its magic.

I haven't tried it, but as a work around, I imagine I could omit the asp-items and do it something like this:

<select asp-items="@items">
    @foreach (var item in items)
    {
        <option value="@item.Value" localize-content>@item.Text</option>
    }
</select>

But I thought I'd ask in case this is isn't required. :)

Thanks, as always.
Morgan

IStringLocalizer missing?

.NET 5
XLocalizer 1.0.0

I'm really excited about being able to store my resources in a database instead of a .resx file.

I followed these instructions to get the XDbResources and XDbCultures tables in my database with seeded data. That worked.

Seeded data

I then wanted to see it pulling the translations from my database, so I went to my _ViewImports.cshtml file and added:

@addTagHelper *, XLocalizer.TagHelpers

I then went to my Index.cshtml file and added the following line to the top (I'd usually add it to _ViewImports, but it didn't work):

@inject IStringLocalizer _localizer

But it doesn't seem to find IStringLocalizer. I had a quick search in the LaZiya/XLocalizer.TagHelpers for the interface, but couldn't find it. Has it been renamed? I got the instructions from: https://docs.ziyad.info/en/XLocalizer/v1.0/localizing-views.md (right at the bottom).

Reference error

The error is:

The type or namespace name 'IStringLocalizer' could not be found

I may be doing something completely stupid, but would appreciate your insight.

Thanks as always,
Morgan

IIS Event Viewer log error: could not find the resource among the resources embedded in the assembly Core 3.1

Hi, first many thanks to you for your efforts.

I have an issue that happens after I publish my project.
These errors are shown in the IIS Event Viewer:

Category: XLocalizer.Resx.ResxResourceProvider
EventId: 0
RequestId: 8000004d-0000-e300-b63f-84710c7967bb
RequestPath: /
SpanId: |24337861-4126c121f82e06f5.
TraceId: 24337861-4126c121f82e06f5
ParentId: 
ActionId: 83cffa14-308a-46c3-adf2-312122ec663a
ActionName: /Index

Could not find the resource "EShoping.WebUI.LocalizationResources.ViewLocalizationResource.resources" among the resources "" embedded in the assembly "EShoping.WebUI", nor among the resources in any satellite assemblies for the specified culture. Perhaps the resources were embedded with an incorrect name.

These are my resources:

image

The container class body:

image

I found this exists in the csproj file:

image

The Startup.cs file:

image

I tried to put this line in csproj file, but nothing happened.

<PropertyGroup>
    <EmbeddedResourceUseDependentUponConvention>false</EmbeddedResourceUseDependentUponConvention
</PropertyGroup>

Could you please check and tell me what should I check and do for this?

Thanks.

Exception: Cannot create a DbSet for 'XDbResource' because this type is not included in the model for the context.

Hi Ziya

This exception (pasted at the end) is thrown whenever I use an EditorTemplate. For example:

@Html.LabelFor(model => model.LoginViewModel.Email) // doesn't throw exception here
@Html.EditorFor(model => model.LoginViewModel.Email, new { htmlAttributes = new { @class = "form-control" } })

I think it's saying it can't find the XDbResource entity in my DbContext. I'm not using the XDbResource entity directly, I've created my own classes that implement IXDbResource. This has worked perfectly when using the IStringLocalizer in my controllers.

My DbContext looks like this:

public class DashboardDbContext : IdentityDbContext<IdentityUser>, IDashboardDbContext, ILocalizationDbContext
    {
        // DI, ctor and other DbSets

        public DbSet<XDbCulture> XDbCultures { get; set; }

        public DbSet<DomainLocalizationResource> DomainLocalizationResources { get; set; }

        public DbSet<UserLocalizationResource> UserLocalizationResource { get; set; }

       // methods...
   }

In my Startup.cs:

public void ConfigureServices(IServiceCollection services)
        {
            // other configurations

            services.AddRazorPages()
                .AddRazorPagesOptions((ops) => {
                    ops.Conventions.Insert(0, new RouteTemplateModelConventionRazorPages());
                })
                .AddXDbLocalizer<DashboardDbContext>((opts) =>
                {
                    opts.UseExpressMemoryCache = false;
                });
        }

Any idea what's going on? I've probably misconfigured XLocalizer. Maybe XLocalizer look specifically for the XDbResource DbSet when attempting to localize EditorTemplates?

Thanks, as always.
Morgan

System.InvalidOperationException: Cannot create a DbSet for 'XDbResource' because this type is not included in the model for the context.
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.get_EntityType()
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.CheckState()
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.get_EntityQueryable()
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.System.Linq.IQueryable.get_Provider()
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.AsNoTracking[TEntity](IQueryable`1 source)
   at XLocalizer.DB.EF.EFDbResourceProvider`1.TryGetValue[TResource](String key, String& value)
   at XLocalizer.DB.DbStringLocalizer`1.GetLocalizedString(String name, Object[] arguments)
   at XLocalizer.DB.DbStringLocalizer`1.get_Item(String name, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.DataAnnotations.ValidationAttributeAdapter`1.GetErrorMessage(ModelMetadata modelMetadata, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.DataAnnotations.RequiredAttributeAdapter.GetErrorMessage(ModelValidationContextBase validationContext)
   at Microsoft.AspNetCore.Mvc.DataAnnotations.RequiredAttributeAdapter.AddValidation(ClientModelValidationContext context)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.DefaultValidationHtmlAttributeProvider.AddValidationAttributes(ViewContext viewContext, ModelExplorer modelExplorer, IDictionary`2 attributes)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ValidationHtmlAttributeProvider.AddAndTrackValidationAttributes(ViewContext viewContext, ModelExplorer modelExplorer, String expression, IDictionary`2 attributes)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.DefaultHtmlGenerator.AddValidationAttributes(ViewContext viewContext, TagBuilder tagBuilder, ModelExplorer modelExplorer, String expression)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.DefaultHtmlGenerator.GenerateInput(ViewContext viewContext, InputType inputType, ModelExplorer modelExplorer, String expression, Object value, Boolean useViewData, Boolean isChecked, Boolean setId, Boolean isExplicitValue, String format, IDictionary`2 htmlAttributes)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.DefaultHtmlGenerator.GenerateTextBox(ViewContext viewContext, ModelExplorer modelExplorer, String expression, Object value, String format, Object htmlAttributes)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.HtmlHelper.GenerateTextBox(ModelExplorer modelExplorer, String expression, Object value, String format, Object htmlAttributes)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.HtmlHelper.TextBox(String expression, Object value, String format, Object htmlAttributes)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.DefaultEditorTemplates.GenerateTextBox(IHtmlHelper htmlHelper, String inputType)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.DefaultEditorTemplates.EmailAddressInputTemplate(IHtmlHelper htmlHelper)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.TemplateRenderer.Render()
   at Microsoft.AspNetCore.Mvc.ViewFeatures.TemplateBuilder.Build()
   at Microsoft.AspNetCore.Mvc.ViewFeatures.HtmlHelper.GenerateEditor(ModelExplorer modelExplorer, String htmlFieldName, String templateName, Object additionalViewData)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.HtmlHelper`1.EditorFor[TResult](Expression`1 expression, String templateName, String htmlFieldName, Object additionalViewData)
   at Microsoft.AspNetCore.Mvc.Rendering.HtmlHelperEditorExtensions.EditorFor[TModel,TResult](IHtmlHelper`1 htmlHelper, Expression`1 expression, Object additionalViewData)
   at Caforb.DashboardUI.Pages.Pages_Login.ExecuteAsync() in Z:\caforb\Src\Caforb.DashboardUi\Pages\Login.cshtml:line 19
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, Boolean invokeViewStarts)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, String contentType, Nullable`1 statusCode)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, String contentType, Nullable`1 statusCode)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|29_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Microsoft.AspNetCore.Localization.RequestLocalizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

RESX file reload at startup

Does the library support reloading RESX file contents at runtime? As I understand ASP.NET Core does not support this since the resource files are compiled. I can have the resource files copied to the output folder, but changing them does not have any effect.

I know it's not the best use case, but this is what an ASP.NET MVC 5 app that I'm migrating to ASP.NET 6 does - customers have the ability to change the texts by changing the RESX files in App_GlobalResources.

Found the library here: https://stackoverflow.com/questions/66336205/update-resx-files-at-runtime-in-net-core

localize-att- and localize-att-args-

Hi Ziya

Hope you're well.

I'm trying to localize an attribute with arguments. To achieve something like this:

<input placeholder="Please provide your name"/>

I was hoping to something like:

<input localize-att-placeholder="Please provide your {0}" />

But I can't figure out how to pass the arguments to localize-att-. Is this possible? I've probably missed it in the docs.

Thanks as always,
Morgan

Culture in URL required?

Hi Ziya

I'm having a little bit of an issue with culture routing in my application. I've not had this problem before, so I'm wondering if something has changed in XLocalizer, or whether I'm doing something silly again.

Here's my Startup.cs after following Using DB as Localization Source:

public void ConfigureServices(IServiceCollection services)
{
        // stuff 
        services.Configure<RequestLocalizationOptions>(opts =>
        {
            var cultures = new CultureInfo[]
            {
                new CultureInfo("en")
                , new CultureInfo("cy")
            };
        
            opts.SupportedCultures = cultures;
            opts.SupportedUICultures = cultures;
            opts.DefaultRequestCulture = new RequestCulture("en");
            opts.RequestCultureProviders.Insert(0, new RouteSegmentRequestCultureProvider(cultures));
        });
        
        services.AddRazorPages()
            .AddMvcOptions((opts) => {
                opts.Filters.Add(new AuthorizeFilter());
                opts.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
            })
            .AddRazorPagesOptions((ops) => {
                ops.Conventions.Insert(0, new RouteTemplateModelConventionRazorPages());
            })
            .AddXDbLocalizer<DashboardDbContext, DummyTranslator, AppLocalizationResource>((opts) =>
            {
                opts.AutoTranslate = false;
                opts.UseExpressMemoryCache = false;
            });

        // more stuff 
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();
    app.UseRequestLocalization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

If I make a request to /, I recieve the Index page in English--perfect.

If they make a request to /Dashboard (for example), the routing seems to break. Instead of seeing the Dashboard page, I still see Index page. However, if I put /en/Dashboard in the URL manually, the Dashboard page displays as expected.

My understanding was if there is no culture in the URL, the fallback one will be used instead. I read Culture fallback behaviour and that sounds ideal, I just can't seem to get it to work.

Any idea what's going on? Maybe I need to update something in the app.UseEnpoints()?

Thank you
Morgan

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.