c# - 本地化与定制

标签 c# asp.net-mvc delphi localization asp.net-web-api

我目前正在编写现有 (Delphi) 桌面软件产品的 ASP.NET MVC 4 网络版本(使用 Razor View 引擎),目前允许客户(企业)完全自定义所有文本他们的应用程序实例,既要对其进行本地化,又要将其定制以适应他们的特定环境。

例如术语-

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

可能全部更改为业务中使用的个别术语。

目前,这种定制只是在存储在应用程序数据库中的文本字符串中完成,并在 Delphi 数据库中的每个表单加载时进行比较和加载。 IE。表单上的每个字符串都与数据库英语字符串进行比较,如果可用,则根据所选语言环境在表单上呈现替换。我认为这既不是可扩展的,也不是特别高效的。

我个人对本地化方法中发生的自定义想法也不太满意,应用程序中的每个字符串都可以由最终客户更改 - 这可能会导致文本一致性方面的支持问题,以及在哪里混淆说明被错误更改或未及时更新。应用程序中有许多字符串可能不应更改,除了将它们本地化为用户的区域设置 - 本地语言和/或格式约定。

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

然而,这确实给我们如何处理这些自定义术语带来了问题。

我目前认为我们应该为这些术语创建一个单独的 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
    );

这可能会在需要时读入 Dictionary 并由 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);
        }
    }
}

然后我可以有一个公开公共(public)属性的类,返回一个基于此字典值 RESX 文件中的值的字符串 - 以非空者为准。像-

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

然后可以根据需要使用字符串格式将它们添加到更大的本地化字符串中,或​​者单独用作菜单等的标签。

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

这看起来是一种可行的方法吗?其他开发者是如何解决这个问题的?

提前致谢。

最佳答案

我曾经设计过一个 MVC 应用程序,其中可以更改任何字符串。在我的例子中,它是为了处理其他语言,但可以想象你可以为了美观而改变任何东西。并且该系统有可能被推广到其他商店,他们可能会用不同的名字来称呼相同的东西(你说“延期付款”,我说“租赁付款”等)

警告:此解决方案与全局化和本地化无关(例如,从左到右、单词/动词顺序 - 它只需要做它所做的!)

它还考虑了美国英语 (en-US) 与英国英语 (en-GB) 与澳大利亚英语 (en-AU) 的可能性。

最后,在数据库中创建了一张Locale表:

_id   _localeName   _idRoot
---------------------------
 1     en-GB        null
 2     en-US        1
 3     en-AU        2

请注意 US 和 AU 如何有效地将 en-GB 作为其父级。因此,en-GB 在我们的翻译表中包含了可以在应用程序中使用的所有可以想到的字符串:

_id   _idCulture   _from           _to
--------------------------------------
1      1           msgyes          Yes
2      1           msgno           No
3      1           msgcolour       Colour
4      2           msgcolour       Color

现在,在应用程序初始化期间,有一个指定文化的配置标志,在我的例子中恰好是 en-AU。系统查找文化树(en-AU 派生自 en-GB),并将所有翻译自下而上加载到字典缓存中。因此,任何 en-AU 的特定翻译都会覆盖 GB 的。

因此,根据您的情况进行描述 - 您的数据库中无论如何都会有所有翻译,这是您的默认设置。当客户希望自定义文本时,他们基本上会得到一个新节点(或者在我的示例中是派生的文化),然后您再次构建缓存。他们自定义的任何条款都会覆盖默认设置。您不再需要担心完成了哪些条款,它就是有效。

关于c# - 本地化与定制,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17522102/

相关文章:

delphi - Indy TCPClient OnDisconnect 事件不起作用

c# - 从 C# 中通过引用传递,通过 C.L.I.到非托管 C++

c# - 如何将 List<string> 转换为 List 中的逗号分隔引号字符串

asp.net - 如何使用 multipart/form-data 进行 ASP.NET MVC Ajax 表单发布?

c# - 如何在页面加载时设置 MVCSitemap 节点角色属性?

delphi - 通过 DataSnap 的 TCP/IP 连接

c# - 是否可以在 C# 中创建没有类的对象?

c# - 如何在运行时替换静态类

asp.net-mvc - 如何在 ASP.NET MVC 中捕获 g-recaptcha-response?

delphi - 函数的结果总是被初始化吗?