本地化与定制

pwd*_*dst 5 c# delphi asp.net-mvc localization asp.net-web-api

我目前正在编写一个现有(Delphi)基于桌面的软件产品的ASP.NET MVC 4 Web版本(使用Razor视图引擎),该产品目前允许客户(企业)完全自定义其实例中的所有文本.应用程序,既可以本地化,也可以根据特定环境进行自定义.

例如,条款─

  • 我的任务
  • 制品
  • 工作流程
  • 设计

可能会改变为业务中使用的个别术语.

目前,这种自定义只是在存储在应用程序数据库中的文本字符串中完成,并在Delphi数据库中的每个表单加载中进行比较和加载.即,将表单上的每个字符串与数据库英语字符串进行比较,并在表单上呈现基于所选语言环境的替换(如果可用).我觉得这不是可扩展的,也不是特别高效的.

我也不熟悉在本地化方法中发生定制的想法,应用程序中的每个字符串都可以由最终客户更改 - 它可能导致文本一致性方面的支持问题,以及指令不正确的混淆更改或未更新.应用程序中有许多字符串,除了将它们本地化为用户的语言环境(本地语言和/或格式约定)之外,可能不应该更改.

我个人宁愿坚持使用RESX资源文件和资源键而不是字符串匹配来本地化应用程序的Web版本的ASP.NET API和约定.这比字符串匹配要灵活得多,其中字符串可能具有不同的上下文或大小写,并且不能简单地进行大量更改(许多英语单词在不同的上下文中可能具有不同的含义,并且可能不会在其他上下文中映射到相同的含义集)语言),关键是避免往返数据库以获取获取页面所需的字符串,并且还允许使用围绕标准RESX文件的大量工具轻松进行转换.这也意味着不需要自定义实现来维护或记录未来的开发人员.

然而,这确实给出了我们如何处理这些自定义术语的问题.

我目前正在考虑我们应该为这些术语设置单独的RESX文件,其中列出了给定语言环境的默认值.然后我会创建一个新的数据库表,就像这样

CREATE TABLE [dbo].[WEB_CUSTOM_TERMS] 
    (
        [TERM_ID] int identity primary key,
        [COMPANY_ID] int NOT NULL, -- Present for legacy reasons
        [LOCALE] varchar(8) NOT NULL,
        [TERM_KEY] varchar(40) NOT NULL,
        [TERM] nvarchar(50) -- Intentionally short, this is to be used for single words or short phrases
    );
Run Code Online (Sandbox Code Playgroud)

这可能会在需要时读入Dictionary <string,string>并由IIS缓存以提供查找,而不会延迟连接到SQL Server并执行查询.

public static class DatabaseTerms
{
    private static string DictionaryKey
    {
       get { return string.Format("CustomTermsDictionary-{0}", UserCulture); }
    }

    private static string UserCulture
    {
        get { return System.Threading.Thread.CurrentThread.CurrentCulture.Name; }
    }

    public static Dictionary<string, string> TermsDictionary
    {
        get
        {
            if (HttpContext.Current.Cache[DictionaryKey] != null)
            {
                var databaseTerms = HttpContext.Current.Cache[DictionaryKey] as Dictionary<string, string>;

                if (databaseTerms != null)
                {
                    return databaseTerms;
                }
            }

            var membershipProvider = Membership.Provider as CustomMembershipProvider;

            int? companyId = null;

            if (membershipProvider != null)
            {
                companyId = CustomMembershipProvider.CompanyId;
            }

            using (var context = new VisionEntities())
            {
                var databaseTerms = (from term in context.CustomTerms
                                     where (companyId == null || term.CompanyId == companyId) &&
                                     (term.Locale == UserCulture)
                                     orderby term.Key
                                     select term).ToDictionary(t => t.Key, t => t.Text);

                HttpContext.Current.Cache.Insert(DictionaryKey, databaseTerms, null, DateTime.MaxValue,
                    new TimeSpan(0, 30, 0), CacheItemPriority.BelowNormal, null);

                return databaseTerms;
            }
        }
        set
        {
            if (HttpContext.Current.Cache[DictionaryKey] != null)
            {
                HttpContext.Current.Cache.Remove(DictionaryKey);
            }
            HttpContext.Current.Cache.Insert(DictionaryKey, value, null, DateTime.Now.AddHours(8),
                  new TimeSpan(0, 30, 0), CacheItemPriority.BelowNormal, null);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我可以有一个暴露公共属性的类,根据这个字典值 RESX文件中的值返回一个字符串- 取其为空.就像是-

public static class CustomTerm
{
    public static string Product
    {
        get
        {
            return (DatabaseTerms.TermsDictionary.ContainsKey("Product") ?
                DatabaseTerms.TermsDictionary["Product"] : CustomTermsResources.Product);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,如果需要,可以使用字符串格式将这些字符串添加到更大的本地化字符串,或者将它们自己用作菜单等

这种方法的主要缺点是需要提前预测最终客户可能希望定制的条款,但我确实认为这可能是两全其美的.

这看起来像一个可行的方法,其他开发人员如何处理这个问题?

提前致谢.

stu*_*sme 2

我们的应用程序中有类似的设置,我们允许某些模块具有自定义名称以适合客户品牌。

此解决方案的第一步是我们在运行时了解客户端上下文,并将其填充到 HttpContext.Items 中。

对于那些可以自定义的项目,我们引入了包含基本密钥的资源文件。如果企业需要定制我们​​在密钥名称前添加前缀(即Client_key)

一旦所有这些都就位,就可以通过简单的合并来获取自定义或默认值。

Resx 文件片段

<data name="TotalLeads" xml:space="preserve">
  <value>Total Leads</value>
</data>
<data name="Client_TotalLeads" xml:space="preserve">
  <value>Total Prospects</value>
</data>
Run Code Online (Sandbox Code Playgroud)

用于处理自定义资源和基础资源之间切换的类

public static class CustomEnterpriseResource
{
    public static string GetString(string key)
    {
        return GetString(key, Thread.CurrentThread.CurrentUICulture);
    }

    public static string GetString(string key, string languageCode)
    {
        return GetString(key, new CultureInfo(languageCode));
    }

    public static string GetString(string key, CultureInfo cultureInfo)
    {
        var customKey = ((EnterpriseContext)HttpContext.Current.Items[EnterpriseContext.EnterpriseContextKey]).ResourcePrefix + key;

        return Resources.Enterprise.ResourceManager.GetString(customKey, cultureInfo) 
            ?? Resources.Enterprise.ResourceManager.GetString(key, cultureInfo);
    }
}
Run Code Online (Sandbox Code Playgroud)

另外,为了协助视图,我们为此创建了一个 html 帮助器。

public static class EnterpriseResourceHelper
{
    /// <summary>
    /// Gets a customizable resource
    /// </summary>
    /// <param name="helper">htmlHelper</param>
    /// <param name="key">Key of the resource</param>
    /// <returns>Either enterprise customized resource or base resource for current culture.</returns>
    public static string EnterpriseResource(this HtmlHelper helper, string key)
    {
        return CustomEnterpriseResource.GetString(key);
    }
}
Run Code Online (Sandbox Code Playgroud)