Multi-Language OWIN

Hello,
I’m trying to add multi-language to my DotVVM web app. I’m currently using OWIN.
I have adapted my DotvvmStatup file for my default page:

config.RouteTable.Add("Default", "{Lang?:length(2)}", "Views/default.dothtml", new { Language = "en"}, presenterFactory: LocalizablePresenter.BasedOnParameter("Lang"));

and I’ve added to my MasterPageViewModel:

public string CurrentCulture => Thread.CurrentThread.CurrentCulture.Name;
[FromRoute("Lang")]
public string CurrentLanguage { get; set; }

I also created 2 RESX files:

  • Resources/Web/Strings.resx
  • Resources/Web/Strings.en-EN.resx

I tried running my web app with and without the “en” route and the page is displayed correctly (without the multi-language).

Using OWIN, how do I setup my page to use the correct resx file? I have checked the github repo but it’s using .net core.

I’m able to import the text from 1 resx file into my Page file using {{resource: Web.Strings.NavbarMenu1}}, but how can I use the correct language file checking the CurrentLanguage (dynamically)?

I was able to make it work but I had to remove this from my head tag:

<head>
...
<title>SomeTitle {{value: !string.IsNullOrWhiteSpace(PageTitle) ? " - " + PageTitle : null}}</title>
...
</head>

throws: System.IndexOutOfRangeException. This PageTitle only works if I remove the Resources.resx file.
Why is it happening?

That is indeed very strange. Could you please share the stack trace of the IndexOutOfRange exception? It’s likely going to be some silly bug on our side.

I’m sorry, I don’t know how localization works on .NET Framework. However, you name the resource file en-EN which is an invalid culture (AFAIK), if you want British English, it’s en-GB. Maybe this could be causing a problem, otherwise your code looks fine to me, the LocalizablePresenter should set the CurrentCulture and CurrentUICulture variables, can you please verify they are set to the expected value?

I’m still learning about localizations too. I’ll fix this issue about en-EN first and see if it fixes it. I tried with “en” only too but it wasn’t working.

Thank you again!

I renamed the resx file to Texts.en-GB.resx and now

{{resource: Texts.HomePageHeaderButton}}

throws

System.IndexOutOfRangeException

My DotvvmStartup file has the following route config:

config.RouteTable.Add("Default", "{Lang?:length(2)}", "Views/default.dothtml", presenterFactory: LocalizablePresenter.BasedOnParameter("Lang"));

The DefaultCulture is set to “pt-PT”, I tried with new { Lang = "pt" } and whitout it.

I already have Access modifier public for Texts.resx. I also tried to create a Texts.pt-PT.resx file too but still same error

Could you please share the exception stack trace (or the entire error page)?

System.IndexOutOfRangeExceptionÍndice fora dos limites da matriz.
{
ClassName:"System.IndexOutOfRangeException"
Message:"Índice fora dos limites da matriz."
Data:null
InnerException:null
HelpURL:null
StackTraceString:" em FastExpressionCompiler.ImTools.FHashMap.GetOrAddValueRefByHash[K,V,TEq,TEntries](FHashMap`4& map, K key, Boolean& found)\r\n em FastExpressionCompiler.ImTools.FHashMap.GetOrAddValueRef[K,V,TEq,TEntries](FHashMap`4& map, K key, Boolean& found)\r\n em FastExpressionCompiler.Tools.FindConvertOperator(Type type, Type sourceType, Type targetType)\r\n em FastExpressionCompiler.ExpressionCompiler.EmittingVisitor.TryEmitConvert(UnaryExpression expr, IReadOnlyList`1 paramExprs, ILGenerator il, ClosureInfo& closure, CompilerFlags setup, ParentFlags parent)\r\n em FastExpressionCompiler.ExpressionCompiler.EmittingVisitor.TryEmit(Expression expr, IReadOnlyList`1 paramExprs, ILGenerator il, ClosureInfo& closure, CompilerFlags setup, ParentFlags parent, Int32 byRefIndex)\r\n em FastExpressionCompiler.ExpressionCompiler.EmittingVisitor.TryEmitAssignToParameterOrVariable(ParameterExpression left, Expression right, ExpressionType nodeType, Boolean isPost, Type exprType, IReadOnlyList`1 paramExprs, ILGenerator il, ClosureInfo& closure, CompilerFlags setup, ParentFlags parent, Int32 resultVar)\r\n em FastExpressionCompiler.ExpressionCompiler.EmittingVisitor.TryEmitArithmeticAndOrAssign(Expression left, Expression right, Type exprType, ExpressionType nodeType, Boolean isPost, IReadOnlyList`1 paramExprs, ILGenerator il, ClosureInfo& closure, CompilerFlags setup, ParentFlags parent)\r\n em FastExpressionCompiler.ExpressionCompiler.EmittingVisitor.TryEmit(Expression expr, IReadOnlyList`1 paramExprs, ILGenerator il, ClosureInfo& closure, CompilerFlags setup, ParentFlags parent, Int32 byRefIndex)\r\n em FastExpressionCompiler.ExpressionCompiler.EmittingVisitor.TryEmit(Expression expr, IReadOnlyList`1 paramExprs, ILGenerator il, ClosureInfo& closure, CompilerFlags setup, ParentFlags parent, Int32 byRefIndex)\r\n em FastExpressionCompiler.ExpressionCompiler.TryCompileBoundToFirstClosureParam(Type delegateType, Expression bodyExpr, IReadOnlyList`1 paramExprs, Type[] closurePlusParamTypes, Type returnType, CompilerFlags flags)\r\n em FastExpressionCompiler.ExpressionCompiler.CompileFast[TDelegate](LambdaExpression lambdaExpr, Boolean ifFastFailedReturnNull, CompilerFlags flags)\r\n em DotVVM.Framework.Compilation.ViewCompiler.ViewCompilingVisitor.VisitView(ResolvedTreeRoot view)\r\n em DotVVM.Framework.Compilation.ViewCompiler.DefaultViewCompiler.<>c__DisplayClass6_0.<CompileViewCore>b__0()\r\n em DotVVM.Framework.Compilation.ViewCompiler.DefaultViewCompiler.<>c__DisplayClass9_0.<CompileView>b__0()\r\n em DotVVM.Framework.Compilation.DefaultControlBuilderFactory.<>c__DisplayClass13_1.<CreateControlBuilder>b__1()\r\n em System.Lazy`1.CreateValue()\r\n em System.Lazy`1.LazyInitValue()\r\n em DotVVM.Framework.Runtime.DefaultDotvvmViewBuilder.BuildView(IDotvvmRequestContext context)\r\n em DotVVM.Framework.Hosting.DotvvmPresenter.<ProcessRequestCore>d__30.MoveNext()\r\n--- Fim do rastreio da pilha da localização anterior em que a excepção foi emitida ---\r\n em System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n em System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n--- Fim do rastreio da pilha da localização anterior em que a excepção foi emitida ---\r\n em System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n em System.Lazy`1.LazyInitValue()\r\n em DotVVM.Framework.Compilation.DefaultControlBuilderFactory.<>c__DisplayClass13_1.<CreateControlBuilder>b__2()\r\n--- Fim do rastreio da pilha da localização anterior em que a excepção foi emitida ---\r\n em System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n em System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n em DotVVM.Framework.Hosting.Middlewares.DotvvmRoutingMiddleware.<Handle>d__3.MoveNext()\r\n--- Fim do rastreio da pilha da localização anterior em que a excepção foi emitida ---\r\n em System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n em DotVVM.Framework.Hosting.Middlewares.DotvvmRoutingMiddleware.<Handle>d__3.MoveNext()\r\n--- Fim do rastreio da pilha da localização anterior em que a excepção foi emitida ---\r\n em System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n em DotVVM.Framework.Hosting.DotvvmMiddleware.<Invoke>d__5.MoveNext()"
RemoteStackTraceString:null
RemoteStackIndex:0
ExceptionMethod:"8\nGetOrAddValueRefByHash\nFastExpressionCompiler, Version=4.0.1.0, Culture=neutral, PublicKeyToken=dfbf2bd50fcf7768\nFastExpressionCompiler.ImTools.FHashMap\nV& GetOrAddValueRefByHash[K,V,TEq,TEntries](FastExpressionCompiler.ImTools.FHashMap`4[K,V,TEq,TEntries] ByRef, K, Boolean ByRef)"
HResult:-2146233080
Source:"FastExpressionCompiler"
WatsonBuckets:null
}
MethodInfo FastExpressionCompiler.Tools.FindConvertOperator(Type type, Type sourceType, Type targetType)
bool FastExpressionCompiler.ExpressionCompiler+EmittingVisitor.TryEmitConvert(UnaryExpression expr, IReadOnlyList<ParameterExpression> paramExprs, ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent)
bool FastExpressionCompiler.ExpressionCompiler+EmittingVisitor.TryEmit(Expression expr, IReadOnlyList<ParameterExpression> paramExprs, ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent, int byRefIndex)
bool FastExpressionCompiler.ExpressionCompiler+EmittingVisitor.TryEmitAssignToParameterOrVariable(ParameterExpression left, Expression right, ExpressionType nodeType, bool isPost, Type exprType, IReadOnlyList<ParameterExpression> paramExprs, ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent, int resultVar)
bool FastExpressionCompiler.ExpressionCompiler+EmittingVisitor.TryEmitArithmeticAndOrAssign(Expression left, Expression right, Type exprType, ExpressionType nodeType, bool isPost, IReadOnlyList<ParameterExpression> paramExprs, ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent)
bool FastExpressionCompiler.ExpressionCompiler+EmittingVisitor.TryEmit(Expression expr, IReadOnlyList<ParameterExpression> paramExprs, ILGenerator il, ref ClosureInfo closure, CompilerFlags setup, ParentFlags parent, int byRefIndex) x 2
object FastExpressionCompiler.ExpressionCompiler.TryCompileBoundToFirstClosureParam(Type delegateType, Expression bodyExpr, IReadOnlyList<ParameterExpression> paramExprs, Type[] closurePlusParamTypes, Type returnType, CompilerFlags flags)
TDelegate FastExpressionCompiler.ExpressionCompiler.CompileFast<TDelegate>(LambdaExpression lambdaExpr, bool ifFastFailedReturnNull, CompilerFlags flags)
void DotVVM.Framework.Compilation.ViewCompiler.ViewCompilingVisitor.VisitView(ResolvedTreeRoot view)
ValueTuple<ControlBuilderDescriptor, Func<Func<IControlBuilderFactory, IServiceProvider, DotvvmControl>>> DotVVM.Framework.Compilation.ViewCompiler.DefaultViewCompiler.CompileViewCore(string sourceCode, string fileName)+() => { }
ValueTuple<ControlBuilderDescriptor, Func<IControlBuilder>> DotVVM.Framework.Compilation.ViewCompiler.DefaultViewCompiler.CompileView(string sourceCode, string fileName)+() => { }
ValueTuple<ControlBuilderDescriptor, Lazy<IControlBuilder>> DotVVM.Framework.Compilation.DefaultControlBuilderFactory.CreateControlBuilder(MarkupFile file)+() => { }
Boxed System.Lazy<T>.CreateValue()
T System.Lazy<T>.LazyInitValue()
DotvvmView DotVVM.Framework.Runtime.DefaultDotvvmViewBuilder.BuildView(IDotvvmRequestContext context)
async Task DotVVM.Framework.Hosting.DotvvmPresenter.ProcessRequestCore(IDotvvmRequestContext context)
T System.Lazy<T>.LazyInitValue()
ValueTuple<ControlBuilderDescriptor, Lazy<IControlBuilder>> DotVVM.Framework.Compilation.DefaultControlBuilderFactory.CreateControlBuilder(MarkupFile file)+() => { }
async Task<bool> DotVVM.Framework.Hosting.Middlewares.DotvvmRoutingMiddleware.Handle(IDotvvmRequestContext context) x 2
async Task DotVVM.Framework.Hosting.DotvvmMiddleware.Invoke(IOwinContext context)

Any idea about this? I’m kinda lost about how to solve this issue.

Hello, the exception is coming from FastExpressionCompiler. Which version are you using? Could you please try changing the version, minimal supported version by DotVVM is 3.3.4.

I have just asked in the FEC discussions if Maksim has any idea. Since, I’m unable to replicate the crash, it’s hard to help any further :confused:

I’m using: DotVVM.Core 4.2.3, DotVVM.Owin 4.2.3 and FastExpressionCompiler vs 4.0.1

The project was previously using DotVVM 3.X

I was able to make it work by creating a new project with Dotvvm 4.X already and the previous code and it worked after renaming the files to Texts.en.resx instead of Texts.en-GB.resx (for exemple). My RouteTable has "{Lang?:length(2)}" so I supposed the file only needs “en” instead of “en-GB”.

For dynamic labels like data coming from database, the only way is storing the data for each language in the database then look for the CurrentCulture to select from table?

For dynamic labels like data coming from database, the only way is storing the data for each language in the database then look for the CurrentCulture to select from table?

Yes, basically.

Regarding the FastExpressionCompiler crash, Maksim Volkau released a new version (4.1.0) which fix the issue.

Maybe I missed some stuffs while migrating to Dotvvm v4.X too…

In case we want to display different urls for different languages (for exemple “/home” for EN and “/inicio” for PT), is it possible to do it from the RouteTable config block ?

config.RouteTable.Add(“Eventos”, “{Lang?:length(2)}/eventos”, “Views/Eventos.dothtml”, new { Lang = “pt” }, presenterFactory: _presenterFactory);

I wanted to display “en/events” and “pt/eventos” for each culture but I’m not sure how I can do it.

I don’t know what is the best way to do this, but one way would be to register additional routes with contant Lang parameters:

config.RouteTable.Add(“Eventos_pt”, “{Lang:regex(pt)}/eventos”, ...);
config.RouteTable.Add(“Eventos_en”, “{Lang:regex(en)}/home”, ...);