https://community.sitecore.net/technical_blogs/b/sitecorejohn_blog/posts/repost-overriding-sitecore-39-s-logic-to-determine-the-context-language
https://community.sitecore.net/technical_blogs/b/sitecorejohn_blog/posts/prevent-the-sitecore-asp-net-cms-from-interpreting-url-path-prefixes-as-language-names
Sitecore 支持多语言, 确定上下文语言的默认逻辑是使用以下面的变量:
sc_lang
查询字符串参数 web.config
中指定的DefaultLanguage
设置 今天我们主要说明一下, 如果自定义URL中的语言前缀, Sitecore 默认语言前缀: en
, zh-CN
, zh-TW
, it-IT
...
以简体中文为例: Sitecore默认URL路径中都会包含zh-CN
, http://www.xxx.com/zh-CN/about
把zh-CN
替换为cn
, 访问http://www.xxx.com/cn/about
能够正常显示简体中文页面.
在上篇博客中我们已经实现了此功能, 但是有点复杂, 后来我又做了些尝试,找到了更新简单的方法.
不需要重写LanguageResolver
和 ItemResolver
, 只需要重写StripLanguage
就可以了, LinkProvider
保持不变
我们知道了StripLanguage
pipeline, 它会自动识语言前缀删除并重定向, 我们要做的就是识别自定义语言前缀, 并把它转换为相应的语言,然后再重定向.
实现上面的目标, 要自定义2个pipeline.
Sitecore.Pipelines.PreprocessRequest.StripLanguage
pipeline, 识别自定义语言前缀, 并把它转换为相应的语言,然后再重定向.Sitecore.Pipelines.HttpRequest.LanguageResolver
pipeline. 把客户端请求URL中的cn
转换为zh-CN
语言.CustomLinkProvider
代码片段public class CustomLinkProvider : Sitecore.Links.LinkProvider
{
public override string GetItemUrl(Item item, UrlOptions options)
{
Assert.ArgumentNotNull(item, "item");
var url = base.GetItemUrl(item, options);
//LanguageMappings alias
if (StripLanguage.LanguageMappings.ContainsKey(HttpContext.Current.Request.Url.Host))
{
var alias = StripLanguage.LanguageMappings[HttpContext.Current.Request.Url.Host];
foreach (var key in alias.Keys)
{
if (string.IsNullOrEmpty(alias[key]))
continue;
var lang = alias[key];
if (!new Regex($"^/{lang}(/.*)?$").IsMatch(url))
continue;
url = new Regex($"^/{lang}(/|$)").Replace(url, $"/{key}/");
break;
}
}
return url;
}
}
CustomLinkProvider
配置片段<linkManager set:defaultProvider="CustomLinkProvider">
<providers>
<add name="CustomLinkProvider">
<patch:attribute name="type">Sitecore.Custom.Website.Pipelines.CustomLinkProvider, Sitecore.Custom.Website</patch:attribute>
<patch:attribute name="languageEmbedding">always</patch:attribute>
<patch:attribute name="lowercaseUrls">true</patch:attribute>
</add>
</providers>
</linkManager>
StripLanguage
代码StripLanguage
代码, 直接反编译Sitecore.Kernel.dll, 找到Sitecore.Pipelines.PreprocessRequest.StripLanguage
Copy出来就可以了,
由于在preprocessRequest
pipeline中还没有Sitecore.Context
上下文, 这里配置多站点, 就需要使用Host来区分了.
读取配置片段
public static Dictionary<string, Dictionary<string, string>> LanguageMappings { get; } = new Dictionary<string, Dictionary<string, string>>();
/// <summary>
/// Add Language Mapping
/// cn.xxx.com : cn -> zh-CN
/// </summary>
/// <param name="node"></param>
public void AddLanguageMapping(XmlNode node)
{
var website = node?.Attributes?["host"]?.Value;
if (string.IsNullOrEmpty(website))
{
return;
}
if (!LanguageMappings.ContainsKey(website))
{
LanguageMappings.Add(website, new Dictionary<string, string>());
}
var mapping = LanguageMappings[website];
foreach (XmlNode childNode in node.ChildNodes)
{
var alias = childNode?.Attributes?["alias"]?.Value;
var culture = childNode?.Attributes?["culture"]?.Value;
if (string.IsNullOrEmpty(alias) || string.IsNullOrEmpty(culture))
{
continue;
}
if (!mapping.ContainsKey(alias))
{
mapping.Add(alias, culture);
}
}
}
解析自定义语言
/// <summary>
/// Extracts the name of the language from URL.
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
protected virtual string ExtractCustomLanguageName(HttpRequest request)
{
var languageName = WebUtil.ExtractLanguageName(request.FilePath);
//don't contain current site, return
if (!LanguageMappings.ContainsKey(request.Url.Host))
{
return languageName;
}
//get site mapping
var mapping = LanguageMappings[request.Url.Host];
return mapping != null && mapping.ContainsKey(languageName) ? mapping[languageName] : languageName;
}
完整代码
public class StripLanguage : PreprocessRequestProcessor
{
public static Dictionary<string, Dictionary<string, string>> LanguageMappings { get; } = new Dictionary<string, Dictionary<string, string>>();
/// <summary>
/// Add Language Mapping
/// cn.xxx.com : cn -> zh-CN
/// </summary>
/// <param name="node"></param>
public void AddLanguageMapping(XmlNode node)
{
var website = node?.Attributes?["host"]?.Value;
if (string.IsNullOrEmpty(website))
{
return;
}
if (!LanguageMappings.ContainsKey(website))
{
LanguageMappings.Add(website, new Dictionary<string, string>());
}
var mapping = LanguageMappings[website];
foreach (XmlNode childNode in node.ChildNodes)
{
var alias = childNode?.Attributes?["alias"]?.Value;
var culture = childNode?.Attributes?["culture"]?.Value;
if (string.IsNullOrEmpty(alias) || string.IsNullOrEmpty(culture))
{
continue;
}
if (!mapping.ContainsKey(alias))
{
mapping.Add(alias, culture);
}
}
}
/// <summary>
/// Gets a value indicating whether an attempt to strip language should be always performed.
/// </summary>
/// <value>
/// <c>true</c> if always strip language; otherwise, <c>false</c>.
/// </value>
protected bool AlwaysStripLanguage
{
get;
private set;
}
/// <summary>
/// RedirectUrlPrefixes property
/// </summary>
protected IEnumerable<string> RedirectUrlPrefixes
{
get;
private set;
}
/// <summary>
/// Initializes a new instance of the <see cref="T:Sitecore.Pipelines.PreprocessRequest.StripLanguage" /> class.
/// </summary>
public StripLanguage() : this(Settings.Languages.AlwaysStripLanguage, Settings.RedirectUrlPrefixes)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="T:Sitecore.Pipelines.PreprocessRequest.StripLanguage" /> class.
/// </summary>
/// <param name="alwaysStripLanguage">if set to <c>true</c> always strip language.</param>
/// <param name="redirectUrlPrefixes">The redirect URL prefixes.</param>
protected StripLanguage(bool alwaysStripLanguage, IEnumerable<string> redirectUrlPrefixes)
{
Assert.ArgumentNotNull(redirectUrlPrefixes, "redirectUrlPrefixes");
this.AlwaysStripLanguage = alwaysStripLanguage;
this.RedirectUrlPrefixes = redirectUrlPrefixes;
}
/// <summary>
/// Extracts the name of the language.
/// </summary>
/// <param name="filePath">The file path.</param>
/// <returns></returns>
protected virtual string ExtractLanguageName(string filePath)
{
string str = WebUtil.ExtractLanguageName(filePath);
if (!string.IsNullOrEmpty(str))
{
return str;
}
return null;
}
/// <summary>
/// Extracts the name of the language from URL.
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
protected virtual string ExtractCustomLanguageName(HttpRequest request)
{
var languageName = WebUtil.ExtractLanguageName(request.FilePath);
//don't contain current site, return
if (!LanguageMappings.ContainsKey(request.Url.Host))
{
return languageName;
}
//get site mapping
var mapping = LanguageMappings[request.Url.Host];
return mapping != null && mapping.ContainsKey(languageName) ? mapping[languageName] : languageName;
}
/// <summary>
/// Determines whether custom language is allowed for usage.
/// </summary>
/// <param name="language">The language.</param>
/// <returns>
/// <c>true</c> if custom language is allowed for usage; otherwise, <c>false</c>.
/// </returns>
internal virtual bool IsCustomCultureAllowed(Language language)
{
return LanguageDefinitions.GetLanguageDefinition(language) != null;
}
/// <summary>
/// Determines whether language is valid for stripping from url.
/// </summary>
/// <param name="language">The language.</param>
/// <param name="args">The arguments.</param>
/// <returns>
/// <c>true</c> if is valid for stripping; otherwise, <c>false</c>.
/// </returns>
protected virtual bool IsValidForStrippingFromUrl(Language language, PreprocessRequestArgs args)
{
CultureInfo cultureInfo = language.CultureInfo;
if ((cultureInfo.LCID == 4096 ? false : !cultureInfo.CultureTypes.HasFlag(CultureTypes.UserCustomCulture)))
{
return true;
}
return this.IsCustomCultureAllowed(language);
}
/// <summary>
/// Processes the specified arguments.
/// </summary>
/// <param name="args">
/// The arguments.
/// </param>
public override void Process(PreprocessRequestArgs args)
{
Language language;
Assert.ArgumentNotNull(args, "args");
if (!this.AlwaysStripLanguage)
{
return;
}
if (this.TryExtractLanguage(args, out language) && this.IsValidForStrippingFromUrl(language, args))
{
Sitecore.Context.Language = language;
Sitecore.Context.Data.FilePathLanguage = language;
this.RewriteUrl(args.HttpContext, language);
this.TraceLanguageChange(language.Name);
}
}
/// <summary>
/// Rewrites the URL after removing the language prefix.
/// </summary>
/// <param name="context">
/// The context.
/// </param>
/// <param name="embeddedLanguage">
/// The embedded language.
/// </param>
private void RewriteUrl(HttpContextBase context, Language embeddedLanguage)
{
Assert.ArgumentNotNull(context, "context");
Assert.ArgumentNotNull(embeddedLanguage, "embeddedLanguage");
HttpRequestBase request = context.Request;
string prefix = WebUtil.ExtractLanguageName(request.FilePath);
string empty = request.FilePath.Substring(prefix.Length + 1);
if (!string.IsNullOrEmpty(empty) && empty.StartsWith(".", StringComparison.InvariantCulture))
{
empty = string.Empty;
}
if (string.IsNullOrEmpty(empty))
{
empty = "/";
}
if (!this.UseRedirect(empty))
{
context.RewritePath(empty, request.PathInfo, StringUtil.RemovePrefix('?', request.Url.Query));
return;
}
UrlString urlString = new UrlString(string.Concat(empty, request.Url.Query));
urlString["sc_lang"] = embeddedLanguage.Name;
context.Response.Redirect(urlString.ToString(), true);
}
/// <summary>
/// Adds language changed tracing to the tracer
/// </summary>
/// <param name="languageName">Name of language</param>
protected virtual void TraceLanguageChange(string languageName)
{
Tracer.Info("Language changed to \"{0}\" as request url contains language embedded in the file path.".FormatWith(new object[] { languageName }));
}
/// <summary>
/// Tries to extract language from provided args.
/// </summary>
/// <param name="args">The arguments.</param>
/// <param name="language">The language.</param>
/// <returns><c>true</c> if language was extracted from request url;<c>false</c> otherwise.</returns>
protected virtual bool TryExtractLanguage(PreprocessRequestArgs args, out Language language)
{
//string str = this.ExtractLanguageName(args.HttpContext.Request.FilePath);
string str = this.ExtractCustomLanguageName(HttpContext.Current.Request);
return this.TryParseLanguage(str, out language);
}
/// <summary>
/// Tries to parse the language.
/// </summary>
/// <param name="languageName">Name of the language.</param>
/// <param name="language">The language.</param>
/// <returns><c>true</c> if language can be restored from name;<c>false</c> otherwise.</returns>
internal virtual bool TryParseLanguage(string languageName, out Language language)
{
return Language.TryParse(languageName, out language);
}
/// <summary>
/// The use rewrite.
/// </summary>
/// <param name="filePath">The file path.</param>
/// <returns>The use rewrite.</returns>
private bool UseRedirect(string filePath)
{
Assert.IsNotNullOrEmpty(filePath, "filePath");
return this.RedirectUrlPrefixes.Any<string>((string path) => filePath.StartsWith(path, StringComparison.InvariantCulture));
}
}
StripLanguage
配置片段<preprocessRequest>
<processor type="Sitecore.Habitat.Website.Pipelines.StripLanguage, Sitecore.Habitat.Website" patch:instead="processor[@type='Sitecore.Pipelines.PreprocessRequest.StripLanguage, Sitecore.Kernel']" >
<settings hint="raw:AddLanguageMapping">
<website host="cn.xxx.com">
<mapping alias="cn" culture="zh-cn" />
</website>
</settings>
</processor>
</preprocessRequest>
实现以上两个pipeline, 就可以完成Sitecore自定义语言前缀的功能了, 这个方法比上一篇博客的方法简单多了.
即使前缀是zh
也不会出现上一篇所提到的问题, 因为我们已经把当前语言设置为了zh-CN
.
如果有任何问题或好的解决方案, 欢迎评论.
还没有人评论,抢个沙发吧...