草庐IT

.NET应用系统的国际化-多语言词条服务

I love .net 2023-03-28 原文

上篇文章我们介绍了

VUE+.NET应用系统的国际化-整体设计思路

系统国际化改造整体设计思路如下:

  1. 提供一个工具,识别前后端代码中的中文,形成多语言词条,按语言、界面、模块统一管理多有的多语言词条
  2. 提供一个翻译服务,批量翻译多语言词条
  3. 提供一个词条服务,支持后端代码在运行时根据用户登录的语言,动态获取对应的多语言文本
  4. 提供前端多语言JS生成服务,按界面动态生成对应的多语言JS文件,方便前端VUE文件使用。
  5. 提供代码替换工具,将VUE前端代码中的中文替换为$t("词条ID"),后端代码中的中文替换为TermService.Current.GetText("词条ID")

今天,我们继续介绍多语言词条服务的设计和实现。

一、多语言词条设计

什么是多语言词条,即代码中需要支持多语言的文本。例如后台提示、前端界面的各类显示元素(Label、Button文字、Tooltips、标题、列表标题等等)。这些内容统一抽象为多语言词条。

多语言词条是产品多语言包的组成部分。支持在不同的语言下,显示对应的文本。

 上图中:

 I18NTerm代表多语言词条对象,主要描述了多语言词条的各个属性,主要的几个属性有:

        /// <summary>
        /// 词条的key
        /// </summary>
        public string Code { get; set; }

        /// <summary>
        /// 词条的名称
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 原始文本
        /// </summary>
        public string OriginalText { get; set; }

        /// <summary>
        /// 多语言词条子项
        /// </summary>
        public List<I18NTermItem> TranslateItems { get; set; } = new List<I18NTermItem>();

        /// <summary>
        /// 隶属的产品
        /// </summary>
        public string Product { get; set; }

        /// <summary>
        /// 隶属的关键应用/系统
        /// </summary>
        /// <remarks>
        /// 用于批量打包国际化JS文件
        /// </remarks>
        public string SubSystem { get; set; }

        /// <summary>
        /// 隶属的关键应用/系统编号
        /// </summary>
        /// <remarks>
        /// 用于批量打包国际化JS文件
        /// </remarks>
        public string SubSystemCode { get; set; }

 一条词条,包含多个词条子项I18NTermItem,每一个词条子项,都代表了一种语言的翻译结果

 public class I18NTermItem : CacheElement
    {
        /// <summary>
        /// 词条ID
        /// </summary>
        public string TermID { get; set; }

        /// <summary>
        /// 语言
        /// </summary>
        public string Language { get; set; }

        /// <summary>
        /// 翻译的文本
        /// </summary>
        public string TranslateText { get; set; }

        /// <summary>
        /// 用户自定义文本
        /// </summary>
        public string CustomText { get; set; }

        public string GetText()
        {
            if (string.IsNullOrEmpty(CustomText))
            {
                return TranslateText;
            }

            return CustomText;
        }
    }

 二、多语言词条管理服务

 有了多语言词条对象后,需要增加其对应的多语言词条管理服务,用于对词条的增删查改

 先定义一个多语言词条管理的接口II18NTermManageService

public interface II18NTermManageService
    {
        void Add(I18NTerm term);

        void Remove(string termId);

        void AddTerms(List<I18NTerm> terms);

        void RemoveTerms(List<string> terms);

        void Update(I18NTerm term);

        I18NTerm GetTerm(string termId);

        List<I18NTerm> GetTerms();

        List<I18NTerm> GetTerms(string sourceId);

        List<I18NTerm> GetTermsByApplication(string applicationId);

        List<I18NTerm> GetTermByConditions(string applicationId, string sourceId = null, string sourceLocation = null, string Dimension1 = null, string Dimension2 = null, string Dimension3 = null);

    }

这个接口对应的实现中,可以采用EF完成词条数据的持久化操作,在这里不再详细展示了,大家根据需求自行实现即可。

三、多语言词条查询服务

系统在运行时,需要调用词条服务查询各类词条的翻译文本。因此,抽象一个多语言词条查询服务接口II18NTermService

    /// <summary>
    /// 词条查询服务接口
    /// </summary>
    public interface II18NTermService
    {
        /// <summary>
        /// 根据词条编号获取对应的词条翻译
        /// </summary>
        /// <param name="termCode">词条编号</param>
        /// <param name="defaultText">默认值,如果根据编号找不到词条或者词条对应的翻译将返回默认值</param>
        /// <returns></returns>
        string GetText(string termCode, string defaultText);

        /// <summary>
        /// 根据词条编号获取对应的词条翻译并格式化输出
        /// </summary>
        /// <param name="termCode">词条编号</param>
        /// <param name="defaultText">默认值,如果根据编号找不到词条或者词条对应的翻译将返回默认值</param>
        /// <param name="args">包含零个或多个要格式化的对象的对象数组</param>
        /// <returns></returns>
        string GetTextFormatted(string termCode, string defaultText, params object[] args);

        /// <summary>
        /// 根据词条编号获取对应的词条翻译
        /// </summary>
        /// <param name="termCode">词条编号</param>
        /// <param name="language">语言标识</param>
        /// <param name="defaultText">默认值,如果根据编号找不到词条或者词条对应的翻译将返回默认值</param>
        /// <returns></returns>
        string GetTextWithlanguage(string termCode,string language, string defaultText);

        /// <summary>
        /// 根据词条编号获取对应的词条翻译并格式化输出
        /// </summary>
        /// <param name="termCode">词条编号</param>
        /// <param name="language">语言标识</param>
        /// <param name="defaultText">默认值,如果根据编号找不到词条或者词条对应的翻译将返回默认值</param>
        /// <param name="args">包含零个或多个要格式化的对象的对象数组</param>
        /// <returns></returns>
        string GetTextFormattedWithlanguage(string termCode, string language, string defaultText, params object[] args);

        /// <summary>
        /// 批量获取词条,注意:此接口不能在特来电生产环境使用。
        /// </summary>
        /// <param name="termCodes"></param>
        /// <returns></returns>
        Dictionary<string,string> BatchGetText(List<string> termCodes);
    }

这个接口的具体实现中,可以增加词条的Redis缓存和内存缓存,调用II18NTermManageService的实现逻辑,从数据库中查询持久化的词条数据。缓存到内存和Redis中,  以提升查询性能。

例如:

 /// <summary>
        /// 获取词条翻译
        /// </summary>
        /// <param name="termCode">词条编号</param>
        /// <param name="defaultText">默认值,当找不到对应的词条时将返回默认值</param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        public string GetText(string termCode, string defaultText)
        {
            if (string.IsNullOrWhiteSpace(termCode))
                throw new ArgumentNullException($"Term Code is null, {termCode}");
            if (Teld.Core.Session.Service.AppContext.Current.Language == null)
            {
                return defaultText;
            }
            string language = T.Core.Session.Service.AppContext.Current.Language.DisplayCode;

            string key = termCode + "&" + language;

            if (cache.TryGetValue(key, out var val))
            {
                return val;
            }

            var termItem = termManageService.GetTermItem(termCode, language);

            if (termItem == null)
            {
                TermMonitor.NotFound(termCode, language);
                return defaultText;
            }
            else
            {
                string text = termItem.GetText();
                cache[key] = text;
                return text;
            }
        }

以上是多语言词条服务的设计和实现。

分享给大家

 

周国庆

2023/3/11

 

有关.NET应用系统的国际化-多语言词条服务的更多相关文章

  1. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  2. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

  3. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  4. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  5. ruby - 无法运行 Rails 2.x 应用程序 - 2

    我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby​​:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r

  6. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

    刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

  7. ruby - 如何模拟 Net::HTTP::Post? - 2

    是的,我知道最好使用webmock,但我想知道如何在RSpec中模拟此方法:defmethod_to_testurl=URI.parseurireq=Net::HTTP::Post.newurl.pathres=Net::HTTP.start(url.host,url.port)do|http|http.requestreq,foo:1endresend这是RSpec:let(:uri){'http://example.com'}specify'HTTPcall'dohttp=mock:httpNet::HTTP.stub!(:start).and_yieldhttphttp.shou

  8. ruby-on-rails - 如何在我的 Rails 应用程序 View 中打印 ruby​​ 变量的内容? - 2

    我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby​​中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R

  9. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  10. ruby - Net::HTTP 获取源代码和状态 - 2

    我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur

随机推荐