在Asp.Net MVC 3中通过文化和UI文化解决现场本地化问题

时间:2011-07-07 12:01:34

标签: c# asp.net-mvc asp.net-mvc-3 localization

我有一个包含以下字段的地址模型类型:

public string CountyState { get; set; }
public string PostZip { get; set; }

我正在使用DisplayNameAttribute的自定义版本(在.Net 4 DisplayAttribute为我们提供本地化之前编写),可以本地化这两个字段的显示名称。

当然,en-GB"County"的本地"Post Code翻译是en-US,而"State"当然是"Zip Code"en-GB }。

但是,我的MVC网站正在显示不同地点的不同商家的付款页面,这些页面也必须能够翻译成其他语言。因此,UI文化可能是"State",但如果为美国企业付款,则地址字段仍应显示"Zip Code"en-US({{1}的后端文化})。

因此,似乎这些显示字段的单个资源类型/键本地化查找是不够的。我想要根据国家/地区使用不同的模型类型,并且想要编写不同的Html - 随着我的网站增长,这两种解决方案将变得难以管理将通过数据库添加。

我的网站已使用CurrentCulture来表示商家的默认文化(当然是用于格式化货币),而我正在使用CurrentUICulture来表示用户的文化;所以我认为我认为这是一个有点古怪的解决方案:

使用CurrentCulture本地化的字符串查找字段的资源键 - 例如ResourceName_Display_Address_PostZip。当en-GB这将返回DisplayName_GB_PostCode时,但en-US时会返回DisplayName_US_ZipCode

现在使用CurrentUICulture查找其本地化版本。

然后我必须写一个可以包装这个逻辑的属性,以便它与MVC兼容。

一个好的解决方案?或者我错过了一些令人眼花缭乱的事情!?

3 个答案:

答案 0 :(得分:1)

这只是伪代码。我现在不在测试和调试的地方。但是,如果DisplayNameAttribute的自定义版本具有关联的CurrentCulture,您应该能够编写与此类似的扩展方法:

    public static string GetLocalizedDisplayName<TProperty>(Expression<Func<TProperty>> e)
    {
        var modelType = ((MemberExpression)e.Body).Member.ReflectedType.GetType();
        var classProperties = TypeDescriptor.GetProperties(modelType).OfType<CustomAttribute>;
        var prop = from property in classProperties
                   where property.CultureInfo == CultureInfo.CurrentCulture
                   select property;

        return prop != null && !string.IsNullOrEmpty(prop.DisplayName) ? prop.DisplayName : member.Member.Name;
    }

答案 1 :(得分:0)

[Required(ErrorMessageResourceType=typeof(Languages.Resource),ErrorMessageResourceName="PostZip")]
[Display(ResourceType=typeof(Languages.Resource),Name="PostZip")]
public string PostZip { get; set; }

现在为不同的本地人创建不同的.resx文件 例如

Name = PostZip |值=邮政编码[适用于en-US]

Name = PostZip |值=后置代码[适用于en-GB]

---内部控制器-----

 //whatever logic you are looking for, now  set culture manually

CultureInfo ci = new CultureInfo("hi");
System.Threading.Thread.CurrentThread.CurrentUICulture = ci;
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(ci.Name);

----------内部视图--------------

 @Html.LabelFor(model => model.PostZip)

也请参考http://haacked.com/archive/2009/12/12/localizing-aspnetmvc-validation.aspx此链接。

答案 2 :(得分:0)

所以这就是我所做的。

我必须使这个代码对我的特定项目更加本地化,​​因为它依赖于传递的资源类具有类型ResourceManager的公共静态属性System.Resources.ResourceManager。对于由VS'自己的强类型资源访问器类(从.resx文件生成)生成的类,这是正确的,因此我认为它可能符合可重用性。它使用它,因此它可以指定查找字符串时使用的文化。

首先对资源名称进行资源查找(使用CurrentCulture),以用于模型属性的文本;然后它将该字符串泵回另一个资源查找(使用CurrentUICulture)以获取要使用的实际显示名称。

我已经用TODO替换了所有错误检查以保持简洁。

最后我直接从DisplayNameAttribute派生,因为这个新模式与我使用的代码(从RequiredAttribute中删除)不能很好地为我自己现有的代码获取基于UICulture的资源字符串自定义属性。我不喜欢班级名称,但现在想不出更好的东西。

public class CultureDrivenDisplayNameAttribute : DisplayNameAttribute
{
  private readonly string _displayName = null;
  public CultureDrivenDisplayNameAttribute(Type resourceType,
    string resourceNameLookup)
  {
    //TODO: check for null args

    //get the ResourceManager member
    var prop = resourceType.GetProperty(
      "ResourceManager", 
      System.Reflection.BindingFlags.Public 
        | System.Reflection.BindingFlags.Static);

    //TODO: check for null or unexpected property type.

    System.Resources.ResourceManager resourceManager = 
      prop.GetValue(null, null) as System.Resources.ResourceManager;

    //TODO: check for null

    //ResourceManager is used as it lets us specify the culture - we need this
    //to be able to lookup first on Culture, then by UICulture.
    string finalResourceName = 
      resourceManager.GetString(resourceNameLookup, 
        Thread.CurrentThread.CurrentCulture);

    //TODO: check for null/whitespace resource value

    _displayName = resourceManager.GetString(finalResourceName, 
      Thread.CurrentThread.CurrentUICulture);
    //TODO: check for null display name
  }

  public override string DisplayName
  {
    get
    {
      return _displayName;
    }
  }
}

所以,现在我按照以下方式装饰我的地址模型的PostZip / CountyState属性:

[CultureDrivenDisplayName(typeof(StringResources), 
  "ResourceName_AddressDetails_CountyState")]
public string CountyState { get; set; }

[CultureDrivenDisplayName(typeof(StringResources), 
  "ResourceName_AddressDetails_PostZip")]
public string PostZip { get; set; }

然后,在我的中立资源文件中:

ResourceName_AddressDetails_CountyState: "DisplayName_AddressDetails_CountyState"
ResourceName_AddressDetails_PostZip: "DisplayName_AddressDetails_PostZip"
DisplayName_AddressDetails_CountyState: "County/State"
DisplayName_AddressDetails_PostZip: "Post/Zip Code"

因此,如果我没有为给定的文化设置资源文件,地址将显示合理的文本。

但是在我的en-GB资源文件中:

ResourceName_AddressDetails_CountyState: "DisplayName_AddressDetails_CountyState_GB"
ResourceName_AddressDetails_PostZip: "DisplayName_AddressDetails_PostZip_GB"
DisplayName_AddressDetails_CountyState_GB: "County"
DisplayName_AddressDetails_PostZip_GB: "Post Code"

现在,无论UI文化如何,英文网站将始终显示“县”或“邮政编码”;但是UI文化能够通过简单地定义_GB后缀资源字符串的本地化版本来提供它自己的两个短语的本地翻译。

我也意识到我可能需要为整个页面做同样的事情,所以我正在考虑扩展Razor和Aspx视图引擎,以便使用子文件夹自动支持基于文化和UI文化的版本。唯一的问题是如何解释文件夹/en-GB/ - 与后端或前端文化有关!