I keep coming across an i18n requirement where my data (not my UI) needs to be internationalized.
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
}
What are some approaches I could use?
Some things I've tried:-
1) Store a resource key in the db
public class FooEntity
{
...
public string NameKey { get; set; }
public string DescriptionKey { get; set; }
}
- Pros: No need for complicated queries to get a translated entity.
System.Globalization
handles fallbacks for you. - Cons: Translations can't easily be managed by an admin user (have to deploy resource files whenever my
Foo
s change).
2) Use a LocalizableString
entity type
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: All localised strings in the same table. Validation can be performed per-string.
- Cons: Queries are horrid. Have to .Include the LocalizedStrings table once for each localizable string on the parent entity. Fallbacks are hard and involve extensive joining. Haven't found a way to avoid N+1 when retrieving e.g. data for a table.
3) Use a parent entity with all the invariant properties and child entities containing all the localized properties
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: Not as hard (but still too hard!) to get a full translation of an entity into memory.
- Cons: Double the number of entities. Can't handle partial translations of an entity - especially the case where, say, Name is coming from
es
but Description is coming fromes-AR
.
I have three requirements for a solution
Users can edit entities, languages, and translations at runtime
Users can supply partial translations with missing strings coming from a fallback as per System.Globalization
Entities can be brought into memory without running into e.g. N+1 issues