Sitecore custom language prefix in the requested URL 2[Sitecore自定义URL中的语言前缀 2]

Sitecore自定义语言前缀 方式二[更简单][推荐]

引用

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查询字符串参数
  • 请求的URL中路径中的语言前缀
  • 与上下文站点关联的语言cookie
  • 与上下文逻辑站点关联的默认语言
  • 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 能够正常显示简体中文页面.

分析

上篇博客中我们已经实现了此功能, 但是有点复杂, 后来我又做了些尝试,找到了更新简单的方法.
不需要重写LanguageResolverItemResolver, 只需要重写StripLanguage就可以了, LinkProvider保持不变
我们知道了StripLanguage pipeline, 它会自动识语言前缀删除并重定向, 我们要做的就是识别自定义语言前缀, 并把它转换为相应的语言,然后再重定向.
实现上面的目标, 要自定义2个pipeline.

  • 重写Sitecore.Pipelines.PreprocessRequest.StripLanguagepipeline, 识别自定义语言前缀, 并把它转换为相应的语言,然后再重定向.
  • 重写Sitecore.Pipelines.HttpRequest.LanguageResolver pipeline. 把客户端请求URL中的cn转换为zh-CN语言.

1. 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;
        }
    }

1. 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>

2.StripLanguage 代码

StripLanguage代码, 直接反编译Sitecore.Kernel.dll, 找到Sitecore.Pipelines.PreprocessRequest.StripLanguageCopy出来就可以了,
由于在preprocessRequestpipeline中还没有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));
        }
    }

2. 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.
如果有任何问题或好的解决方案, 欢迎评论.

评论

还没有人评论,抢个沙发吧...

Viagle Blog

欢迎来到我的个人博客网站