目前,我正在编写一个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);
}
}