Setting the culture during the inbound request pipeline in a 1:1 multilang site with Vorto
I'm doing a 1:1 multilingual site using Vorto. And while not strictly a question, I'd love to hear comments on how implemented it and also I found a strange behavior with PublishedContentRequest and IContentFinder
Small introduction: my setup has 3 languages (bg, en, fr), the name of each node (thus the URL) is in English only, and content is in Vorto properties.
Vanilla implementation would give me the same /folder/nodeName url for all language versions and I'd have had to differentiate by adding something like ?lang=fr and use the querystring to set the current thread culture in the page.
But this was not a nice solution, not for users with strange URLs and not for the code as was not very clean.
So I decided to dig into Umbraco extensibility and try to make everything more transparent and better-looking.
First step was to make sure URL are automatically generated based on the current culture, so that I don't have to manually add /fr/ or ?lang=fr everytime I needed to write a URL.
For this I created a IUrlProvider by extending the default DefaultUrlProvider. Here is the code:
public class UrlWithLanguage : DefaultUrlProvider
{
public UrlWithLanguage()
: base(UmbracoConfig.For.UmbracoSettings().RequestHandler){ }
public override string GetUrl(UmbracoContext umbracoContext, int id, Uri current, UrlProviderMode mode)
{
var currentCulture = System.Globalization.CultureInfo.CurrentUICulture;
var currentCultureName = currentCulture.TwoLetterISOLanguageName;
var original = base.GetUrl(umbracoContext, id, current, mode);
return "/" + currentCultureName + original;
}
}
With this in place urls are automatically /en/folder/nodeName/
Then I had to make sure that when the request came back in, the language identifier was removed (otherwise umbraco would look for a node /en/folder/nodeName/ and would find nothing) and that the current culture was set to what was the language identified.
To this I created a IContentFinder, extending the ContentFinderByNiceUrl. Here is the code:
public class LanguageAwareContentFinder : ContentFinderByNiceUrl
{
public override bool TryFindContent(PublishedContentRequest contentRequest)
{
string route;
if (contentRequest.HasDomain)
route = contentRequest.Domain.RootNodeId.ToString() + DomainHelper.PathRelativeToDomain(contentRequest.DomainUri, contentRequest.Uri.GetAbsolutePathDecoded());
else
route = contentRequest.Uri.GetAbsolutePathDecoded();
CultureInfo ci = new CultureInfo("en");
if (contentRequest.Uri.Segments.Length > 1)
{
var cultureString = contentRequest.Uri.Segments[1].TrimEnd('/');
var languages = ApplicationContext.Current.Services.LocalizationService.GetAllLanguages().AsQueryable();
var language = languages.SingleOrDefault(l => l.IsoCode == cultureString);
if (language != null)
{
ci = language.CultureInfo;
route = route.Replace("/"+contentRequest.Uri.Segments[1], "/");
}
}
var node = FindContent(contentRequest, route);
if (node == null) return false;
contentRequest.PublishedContent = node;
contentRequest.Culture = ci;
return true;
}
}
Basically I copied the code of the ContentFinderByNiceUrl and I remove the language identifier from the route sent to the FindContent. I also set the contentRequest.Culture to the culture identified in the url.
Unfortunately what I set here is overwritten later, and in the pages I always got en, no matter which was the language in the url.
I fixed the problem by handling the PublishedContentRequest.Prepared event, basically running the same code as above:
private void PublishedContentRequest_Prepared(object sender, EventArgs e)
{
var contentRequest = sender as PublishedContentRequest;
CultureInfo ci = new CultureInfo("en");
if (contentRequest.Uri.Segments.Length > 1)
{
var cultureString = contentRequest.Uri.Segments[1].TrimEnd('/');
var languages = ApplicationContext.Current.Services.LocalizationService.GetAllLanguages().AsQueryable();
var language = languages.SingleOrDefault(l => l.IsoCode == cultureString);
if (language != null)
{
ci = language.CultureInfo;
}
}
contentRequest.Culture = ci;
}
So, back to the question: is this a good approach? Any comments? Do you see any obvious problem I might have not seen?
Then, why is the value of the PublishedContentRequest.Culture reset after the execution of the IContentFinder ends?
Yeah, I know, I've seen them, but my situation is different, as I don't need different URLs per node, and also I didn't want to build my own method for finding content as I fine with just removing the language identifier before processing the standard request.
Also, I was asking about the PublishedContentRequest.Culture and why it's not kept exiting from the IContentFinder.
Version 7.4.2 will being a PublishedContentRequest.Preparing event that can be used to modify the request's Uri before it reaches the finders (though I'm not sure how we would deal with the culture).
That being said... setting the culture, the way you do it, should work. Will investigate.
Mostly a guess but... could it be that you have a "wildcard" hostname set somewhere in your tree? That is, a language-only hostname (no url)? That's the only reason I can see why the Culture you set on the request would change afterwards.
That would be it. The language setting is overriding whatever you set on the PublishedContentRequest, because dealing with these language settings happens after the content finders, because... we need a content to know which branch of the tree to navigate.
The Prepared event runs after the language setting thing, and that is why it works.
I'd suggest removing the language setting, since you don't need it, since you deal with cultures by yourself (ie, turn it back to Inherit). Then it should work.
Setting the culture during the inbound request pipeline in a 1:1 multilang site with Vorto
I'm doing a 1:1 multilingual site using Vorto. And while not strictly a question, I'd love to hear comments on how implemented it and also I found a strange behavior with
PublishedContentRequest
andIContentFinder
Small introduction: my setup has 3 languages (bg, en, fr), the name of each node (thus the URL) is in English only, and content is in Vorto properties.
Vanilla implementation would give me the same
/folder/nodeName
url for all language versions and I'd have had to differentiate by adding something like?lang=fr
and use the querystring to set the current thread culture in the page.But this was not a nice solution, not for users with strange URLs and not for the code as was not very clean.
So I decided to dig into Umbraco extensibility and try to make everything more transparent and better-looking.
First step was to make sure URL are automatically generated based on the current culture, so that I don't have to manually add
/fr/
or?lang=fr
everytime I needed to write a URL.For this I created a
IUrlProvider
by extending the defaultDefaultUrlProvider
. Here is the code:With this in place urls are automatically
/en/folder/nodeName/
Then I had to make sure that when the request came back in, the language identifier was removed (otherwise umbraco would look for a node
/en/folder/nodeName/
and would find nothing) and that the current culture was set to what was the language identified.To this I created a
IContentFinder
, extending theContentFinderByNiceUrl
. Here is the code:Basically I copied the code of the
ContentFinderByNiceUrl
and I remove the language identifier from theroute
sent to theFindContent
. I also set thecontentRequest.Culture
to the culture identified in the url.Unfortunately what I set here is overwritten later, and in the pages I always got
en
, no matter which was the language in the url.I fixed the problem by handling the
PublishedContentRequest.Prepared
event, basically running the same code as above:So, back to the question: is this a good approach? Any comments? Do you see any obvious problem I might have not seen?
Then, why is the value of the
PublishedContentRequest.Culture
reset after the execution of theIContentFinder
ends?Thx Simone
Hi Simone,
My colleague Jeroen Breuer wrote a blog, did a hangout and created a sample project on doing 1-on-1 multilanguage with Vorto
http://24days.in/umbraco/2015/multilingual-vorto-nested-content/
https://www.youtube.com/watch?time_continue=1&v=DWjbJiIUQdk
https://our.umbraco.org/projects/developer-tools/1-1-multilingual-example/
Dave
Yeah, I know, I've seen them, but my situation is different, as I don't need different URLs per node, and also I didn't want to build my own method for finding content as I fine with just removing the language identifier before processing the standard request.
Also, I was asking about the
PublishedContentRequest.Culture
and why it's not kept exiting from theIContentFinder
.Thanks... but no thanks :)
That would be the best approach, pre-7.4.2.
Version 7.4.2 will being a PublishedContentRequest.Preparing event that can be used to modify the request's Uri before it reaches the finders (though I'm not sure how we would deal with the culture).
That being said... setting the culture, the way you do it, should work. Will investigate.
Mostly a guess but... could it be that you have a "wildcard" hostname set somewhere in your tree? That is, a language-only hostname (no url)? That's the only reason I can see why the Culture you set on the request would change afterwards.
Indeed, no domain is specified. Here is my "hostname and cultures" dialog. Shall I specify it? To the main language?
That would be it. The language setting is overriding whatever you set on the PublishedContentRequest, because dealing with these language settings happens after the content finders, because... we need a content to know which branch of the tree to navigate.
The Prepared event runs after the language setting thing, and that is why it works.
I'd suggest removing the language setting, since you don't need it, since you deal with cultures by yourself (ie, turn it back to Inherit). Then it should work.
?
is working on a reply...