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

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 能够正常显示简体中文页面.

分析

实现上面的目标, 要自定义几个pipeline.

  • 重写Sitecore.Links.LinkProvider中的GetItemUrl方法, 在生成的URL中把zh-CN替换为cn.
  • 重写Sitecore.Pipelines.HttpRequest.LanguageResolver pipeline. 把客户端请求URL中的cn转换为zh-CN语言.
  • 自定义ItemResolver继承自Sitecore.Pipelines.HttpRequest.HttpRequestProcessor, 根据请求的URL路径找到相应的Sitecore Item.

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);
            //LanguageResolver alias
            if (LanguageResolver.LanguageMappings.ContainsKey(Context.Site?.Name ?? string.Empty))
            {
                var alias = LanguageResolver.LanguageMappings[Context.Site.Name];
                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.LanguageResolver 代码

public class LanguageResolver : Sitecore.Pipelines.HttpRequest.LanguageResolver
{
    public static Dictionary<string, Dictionary<string, string>> LanguageMappings { get; } = new Dictionary<string, Dictionary<string, string>>();

    public void AddLanguageMapping(XmlNode node)
    {
        var website = node?.Attributes?["name"]?.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);
            }
        }
    }

    public override void Process(HttpRequestArgs args)
    {
        var language = GetCustomLanguage(HttpContext.Current.Request);
        if (language == null)
        {
            base.Process(args);
            return;
        }
        Sitecore.Context.Language = language;
        Tracer.Info("Language changed to \"" + language.Name + "\" as custon language alias.");
    }

    private Language GetCustomLanguage(HttpRequest request)
    {
        var languageName = string.Empty;
        //don't contain current site, return
        if (!LanguageMappings.ContainsKey(Context.Site?.Name ?? string.Empty))
        {
            return null;
        }
        //get site mapping
        var mapping = LanguageMappings[Context.Site.Name];

        //get the language name from url
        var requestUrl = request.Url.AbsolutePath;
        if (requestUrl.Length == 1)
            languageName = "en";
        var alias = requestUrl.Split('/')[1];
        if (mapping == null || !mapping.ContainsKey(alias))
            return null;
        languageName =  mapping[alias];

        Language result;
        return Language.TryParse(languageName, out result) ? result : null;
    }
}

2. LanguageResolver 配置片段

<httpRequestBegin>
    <processor type="Sitecore.Habitat.Website.Pipelines.LanguageResolver, Sitecore.Habitat.Website" patch:instead="processor[@type='Sitecore.Pipelines.HttpRequest.LanguageResolver, Sitecore.Kernel']" >
        <settings hint="raw:AddLanguageMapping">
            <website name="website">
                <mapping alias="cn" culture="zh-cn" />
            </website>
        </settings>
    </processor>
</httpRequestBegin>

3. ItemResolver 代码片段

public class ItemResolver : HttpRequestProcessor
{
    public override void Process(HttpRequestArgs args)
    {
        if (Sitecore.Context.Item != null)
        {
            return;
        }
        if (!LanguageResolver.LanguageMappings.ContainsKey(Context.Site?.Name ?? string.Empty))
        {
            return;
        }
        var mapping = LanguageResolver.LanguageMappings[Context.Site.Name];
        if (HttpContext.Current != null && !string.IsNullOrWhiteSpace(HttpContext.Current.Request.FilePath))
        {
            string prefix = WebUtil.ExtractLanguageName(HttpContext.Current.Request.FilePath);

            if (!string.IsNullOrWhiteSpace(prefix) && mapping.ContainsKey(prefix) && args.Url.ItemPath.StartsWith($"{Context.Site.StartPath}/{prefix}"))
            {
                var item = Sitecore.Context.Database.GetItem(args.Url.ItemPath.ReplaceFirst($"{Context.Site.StartPath}/{prefix}", Context.Site.RootPath), Sitecore.Context.Language, Data.Version.Latest);
                if(item != null)
                {
                    Sitecore.Context.Item = item;
                    Sitecore.Diagnostics.Log.Info($"Item full path: {item.Paths.FullPath}", this);
                }
            }
        }
    }
}

3. ItemResolver 配置片段

<httpRequestBegin>
    <processor type="Sitecore.Habitat.Website.Pipelines.ItemResolver, Sitecore.Habitat.Website" patch:after="processor[@type='Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel']" />
</httpRequestBegin>

结果

截图如下:
https://i.loli.net/2019/06/16/5d051a962ac0d13995.png

正常到这里就应该结束了, 不过还有一点需要说明一下, 如果把zh-CN替换为zh 还需要再多一个步骤.
这是因为Sitecore, 可以正常解析zh语言,它也是中文, 但又有区别于zh-CN, 这没有仔细研究.
以下是调试信息:
zh

Sitecore.Context.Language.CultureInfo
{zh}
    Calendar: {System.Globalization.GregorianCalendar}
    CompareInfo: {CompareInfo - zh}
    CultureTypes: NeutralCultures | InstalledWin32Cultures
    DateTimeFormat: {System.Globalization.DateTimeFormatInfo}
    DisplayName: "Chinese"
    EnglishName: "Chinese"
    IetfLanguageTag: "zh"
    IsNeutralCulture: true
    IsReadOnly: true
    KeyboardLayoutId: 30724
    LCID: 30724
    Name: "zh"
    NativeName: "中文"
    NumberFormat: {System.Globalization.NumberFormatInfo}
    OptionalCalendars: {System.Globalization.Calendar[1]}
    Parent: {}
    TextInfo: {TextInfo - zh}
    ThreeLetterISOLanguageName: "zho"
    ThreeLetterWindowsLanguageName: "CHS"
    TwoLetterISOLanguageName: "zh"
    UseUserOverride: false

zh-CN

Sitecore.Context.Language.CultureInfo
{zh-CN}
    Calendar: {System.Globalization.GregorianCalendar}
    CompareInfo: {CompareInfo - zh-CN}
    CultureTypes: SpecificCultures | InstalledWin32Cultures | FrameworkCultures
    DateTimeFormat: {System.Globalization.DateTimeFormatInfo}
    DisplayName: "Chinese (Simplified, PRC)"
    EnglishName: "Chinese (Simplified, China)"
    IetfLanguageTag: "zh-CN"
    IsNeutralCulture: false
    IsReadOnly: true
    KeyboardLayoutId: 2052
    LCID: 2052
    Name: "zh-CN"
    NativeName: "中文(中国)"
    NumberFormat: {System.Globalization.NumberFormatInfo}
    OptionalCalendars: {System.Globalization.Calendar[1]}
    Parent: {zh-CHS}
    TextInfo: {TextInfo - zh-CN}
    ThreeLetterISOLanguageName: "zho"
    ThreeLetterWindowsLanguageName: "CHS"
    TwoLetterISOLanguageName: "zh"
    UseUserOverride: false

如果一个Item 新增了zh-CN语言版本, 但通过zh去访问, 无法显示zh-CN语言的内容, 因为语言不一样.
为什么? 难道LanguageResolver没有工作? 没有正常解析语言?
是的, Sitecore中有一个StripLanguage pipeline, 它会自动识语言前缀删除并重定向. 所以在LanguageResolver中查找自定义语言前缀zh时没有找到.

重写Sitecore.Pipelines.PreprocessRequest.StripLanguagePipeline, 把让Sitecore跳过处理zh语言前缀.

4. StripLanguage 代码片段

public class StripLanguage : Sitecore.Pipelines.PreprocessRequest.StripLanguage
{
    List<string> IgnorePrefix { get; } = new List<string>();

    public void IgnoreUrlPrefix(XmlNode node)
    {
        var urlPrefix = node?.Attributes?["name"]?.Value;
        if (string.IsNullOrEmpty(urlPrefix))
        {
            return;
        }
        if (!IgnorePrefix.Contains(urlPrefix))
        {
            IgnorePrefix.Add(urlPrefix);
        }
    }

    public override void Process(PreprocessRequestArgs args)
    {
        if (HttpContext.Current != null && !string.IsNullOrWhiteSpace(HttpContext.Current.Request.FilePath))
        {
            string prefix = WebUtil.ExtractLanguageName(HttpContext.Current.Request.FilePath);

            if (!string.IsNullOrWhiteSpace(prefix) && IgnorePrefix.Contains(prefix))
            {
                return;
            }
        }

        base.Process(args);
    }
}

4. 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:IgnoreUrlPrefix">
            <alias name="zh" />
        </settings>
    </processor>
</preprocessRequest>

总结

以上是我在最近的项目中实现的自定义Sitecore语言前缀, 看起来确实比想像的要麻烦一点, 如果你有更好的想法或实现, 欢迎评论.

评论

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

Viagle Blog

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