tutorial net mvc framework foreign first español data code asp entity-framework internationalization

net - Internacionalización de contenidos en Entity Framework.



entity framework tutorial español (2)

Sigo cumpliendo con un requisito de i18n en el que mis datos (no mi UI) deben internacionalizarse.

public class FooEntity { public long Id { get; set; } public string Code { get; set; } // Some values might not need i18n public string Name { get; set } // but e.g. this needs internationalized public string Description { get; set; } // and this too }

¿Cuáles son algunos enfoques que podría usar?

Algunas cosas que he intentado: -

1) Almacenar una clave de recursos en la db

public class FooEntity { ... public string NameKey { get; set; } public string DescriptionKey { get; set; } }

  • Pros: No es necesario realizar consultas complicadas para obtener una entidad traducida. System.Globalization maneja los fallbacks por ti.
  • Contras: las traducciones no pueden ser administradas fácilmente por un usuario administrador (tiene que implementar archivos de recursos cada vez que mi Foo cambie).

2) Usar un tipo de entidad LocalizableString

public class FooEntity { ... public int NameId { get; set; } public virtual LocalizableString Name { get; set; } public int NameId { get; set; } public virtual LocalizableString Description { get; set; } } public class LocalizableString { public int Id { get; set; } public ICollection<LocalizedString> LocalizedStrings { get; set; } } public class LocalizedString { public int Id { get; set; } public int ParentId { get; set; } public virtual LocalizableString Parent { get; set; } public int LanguageId { get; set; } public virtual Language Language { get; set; } public string Value { get; set; } } public class Language { public int Id { get; set; } public string Name { get; set; } public string CultureCode { get; set; } }

  • Pros: Todas las cadenas localizadas en la misma tabla. La validación se puede realizar por cadena.
  • Contras: las consultas son horribles. Debe incluir la tabla LocalizedStrings una vez por cada cadena localizable en la entidad principal. Los fallbacks son difíciles e implican uniones extensas. No he encontrado una forma de evitar N + 1 al recuperar, por ejemplo, datos para una tabla.

3) Utilice una entidad principal con todas las propiedades invariables y las entidades secundarias que contienen todas las propiedades localizadas

public class FooEntity { ... public ICollection<FooTranslation> Translations { get; set; } } public class FooTranslation { public long Id { get; set; } public int ParentId { get; set; } public virtual FooEntity Parent { get; set; } public int LanguageId { get; set; } public virtual Language Language { get; set; } public string Name { get; set } public string Description { get; set; } } public class Language { public int Id { get; set; } public string Name { get; set; } public string CultureCode { get; set; } }

  • Pros: No es tan difícil (¡pero aún así es demasiado difícil!) Obtener una traducción completa de una entidad a la memoria.
  • Contras: Duplica el número de entidades. No se pueden manejar las traducciones parciales de una entidad, especialmente en el caso de que, por ejemplo, el nombre proviene de es pero la descripción proviene de es-AR .

Tengo tres requisitos para una solución

  • Los usuarios pueden editar entidades, idiomas y traducciones en tiempo de ejecución

  • Los usuarios pueden proporcionar traducciones parciales con cadenas faltantes que provienen de un respaldo según System.Globalization

  • Las entidades se pueden llevar a la memoria sin tener que ejecutar, por ejemplo, problemas de N + 1


¿Por qué no tomas lo mejor de ambos mundos? Tenga un CustomResourceManager que maneje la carga de recursos y seleccione la cultura correcta y use un CustomResourceReader que use cualquier tienda de respaldo que desee. Una implementación básica podría tener este aspecto, basándose en la convención de que Resourceky es Typename_PropertyName_PropertyValue. Si por alguna razón la estructura del backingstore (csv / excel / mssql / table structure) necesita cambiar, solo tiene que cambiar la implementación del ResourceReader.

Como un bono adicional, también puse en marcha el proxy real / transparente.

Administrador de recursos

class MyRM:ResourceManager { readonly Dictionary<CultureInfo, ResourceSet> sets = new Dictionary<CultureInfo, ResourceSet>(); public void UnCache(CultureInfo ci) { sets.Remove(ci): } protected override ResourceSet InternalGetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents) { ResourceSet set; if (!sets.TryGetValue(culture, out set)) { IResourceReader rdr = new MyRR(culture); set = new ResourceSet(rdr); sets.Add(culture,set); } return set; } // sets Localized values on properties public T GetEntity<T>(T obj) { var entityType = typeof(T); foreach (var prop in entityType.GetProperties( BindingFlags.Instance | BindingFlags.Public) .Where(p => p.PropertyType == typeof(string) && p.CanWrite && p.CanRead)) { // FooEntity_Name_(content of Name field) var key = String.Format("{0}_{1}_{2}", entityType.Name, prop.Name, prop.GetValue(obj,null)); var val = GetString(key); // only set if a value was found if (!String.IsNullOrEmpty(val)) { prop.SetValue(obj, val, null); } } return obj; } }

Lector de recursos

class MyRR:IResourceReader { private readonly Dictionary<string, string> _dict; public MyRR(CultureInfo ci) { _dict = new Dictionary<string, string>(); // get from some storage (here a hardcoded Dictionary) // You have to be able to deliver a IDictionaryEnumerator switch (ci.Name) { case "nl-NL": _dict.Add("FooEntity_Name_Dutch", "nederlands"); _dict.Add("FooEntity_Name_German", "duits"); break; case "en-US": _dict.Add("FooEntity_Name_Dutch", "The Netherlands"); break; case "en": _dict.Add("FooEntity_Name_Dutch", "undutchables"); _dict.Add("FooEntity_Name_German", "german"); break; case "": // invariant _dict.Add("FooEntity_Name_Dutch", "dutch"); _dict.Add("FooEntity_Name_German", "german?"); break; default: Trace.WriteLine(ci.Name+" has no resources"); break; } } public System.Collections.IDictionaryEnumerator GetEnumerator() { return _dict.GetEnumerator(); } // left out not implemented interface members }

Uso

var rm = new MyRM(); var f = new FooEntity(); f.Name = "Dutch"; var fl = rm.GetEntity(f); Console.WriteLine(f.Name); Thread.CurrentThread.CurrentUICulture = new CultureInfo("nl-NL"); f.Name = "Dutch"; var dl = rm.GetEntity(f); Console.WriteLine(f.Name);

RealProxy

public class Localizer<T>: RealProxy { MyRM rm = new MyRM(); private T obj; public Localizer(T o) : base(typeof(T)) { obj = o; } public override IMessage Invoke(IMessage msg) { var meth = msg.Properties["__MethodName"].ToString(); var bf = BindingFlags.Public | BindingFlags.Instance ; if (meth.StartsWith("set_")) { meth = meth.Substring(4); bf |= BindingFlags.SetProperty; } if (meth.StartsWith("get_")) { // get the value... meth = meth.Substring(4); var key = String.Format("{0}_{1}_{2}", typeof (T).Name, meth, typeof (T).GetProperty(meth, BindingFlags.Public | BindingFlags.Instance |BindingFlags.GetProperty). GetValue(obj, null)); // but use it for a localized lookup (rm is the ResourceManager) var val = rm.GetString(key); // return the localized value return new ReturnMessage(val, null, 0, null, null); } var args = new object[0]; if (msg.Properties["__Args"] != null) { args = (object[]) msg.Properties["__Args"]; } var res = typeof (T).InvokeMember(meth, bf , null, obj, args); return new ReturnMessage(res, null, 0, null, null); } }

Uso de proxy real / transparente

var f = new FooEntity(); f.Name = "Dutch"; var l = new Localizer<FooEntity>(f); var fp = (FooEntity) l.GetTransparentProxy(); fp.Name = "Dutch"; // notice you can use the proxy as is, // it updates the actual FooEntity var localizedValue = fp.Name;


El primero es digno si tienes contenido estático en la base de datos. Por ejemplo, si tiene categorías que el usuario no va a cambiar relativamente. Puedes cambiarlos en el próximo despliegue. No me gusta esta solución personalmente. No considero esto como una buena solución. Esto es solo un escape del problema.

El segundo es el mejor, pero puede causar un problema cuando tiene dos o más campos localizables en una entidad. Puedes simplificarlo un poco y los lenguajes de código duro de esta manera

public class LocalizedString { public int Id { get; set; } public string EnglishText { get; set; } public string ItalianText { get; set; } public string ArmenianText { get; set; } }

El tercero tampoco es bueno. De esta estructura no puedo estar seguro de que todos los nodos (literales, líneas, cadenas, etc.) se traduzcan en una cultura específica.

No generalizar demasiado. Cada problema es un tanto especializado y también necesita una solución especializada. Demasiada generalización hace problemas injustificados.