目前,我正在编写一个ASP.NET MVC 4 Web版本(使用Rayor VIEW引擎)(现有的基于Delphi)的基于桌面的软件产品,目前允许客户(企业)完全定制他们的应用程序实例中的所有文本,既可以本地化,又可以定制它的特定环境。
例如术语-
我的任务
产品
工作流程
设计
可能全部更改为业务中使用的单个术语。
目前,这种定制只需在存储在应用程序数据库中的文本字符串中完成,并在delphi数据库中的每个表单加载时进行比较和加载。也就是说,表单上的每个字符串都将与数据库中的英文字符串进行比较,如果可用,则会在表单上呈现基于所选区域设置的替换字符串。我觉得这不是可伸缩的,也不是特别好用的。
我个人也不喜欢本地化方法中的定制思想,即应用程序中的每个字符串都可以由最终客户更改,这可能导致文本一致性方面的支持问题,以及指令错误更改或不及时更新时的混乱。应用程序中有许多字符串,除了将它们本地化为用户本地语言的区域设置和/或格式化约定之外,可能不应该更改这些字符串。
我个人更愿意使用asp.netapi和约定来本地化应用程序的web版本,使用resx资源文件和资源键,而不是字符串匹配。这比字符串匹配更灵活,其中字符串可能具有不同的上下文或情况,并且不能简单地改变质量(许多英语单词在不同的上下文中可能具有不同的含义,并且可能不映射到其他语言中相同的一组含义),关键是避免往返于数据库以获取字符串。需要获取页面,还允许使用一组围绕标准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
    );

这可能在需要时读入字典并由iis缓存,以提供查找,而不会延迟连接到sql服务器和执行查询。
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);
        }
    }
}

然后我可以有一个公开公共属性的类,根据这个字典值或resx文件中的值返回一个字符串,以不为空的为准。有点像-
public static class CustomTerm
{
    public static string Product
    {
        get
        {
            return (DatabaseTerms.TermsDictionary.ContainsKey("Product") ?
                DatabaseTerms.TermsDictionary["Product"] : CustomTermsResources.Product);
        }
    }
}

如果需要,可以使用字符串格式将它们添加到更大的本地化字符串中,或者它们自己用作菜单等的标签。
这种方法的主要缺点是需要预先预测最终客户可能希望定制的条款,但我确实觉得这可能是两个世界中最好的。
这看起来像是一个可行的方法吗?其他开发人员是如何解决这个问题的?
提前谢谢。

最佳答案

我们在我们的应用程序中有一个类似的设置,我们允许某些模块有一个自定义的名称以适合客户的品牌。
此解决方案的第一步是在运行时了解客户端上下文,并将其填充到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>

类来处理自定义资源和基本资源之间的切换。
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);
    }
}

为了帮助视图,我们为此创建了一个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);
    }
}

08-17 15:03