Localization van dynamische resources

Pagina: 1
Acties:

Acties:
  • 0 Henk 'm!

  • F.West98
  • Registratie: Juni 2009
  • Laatst online: 18:12

F.West98

Alweer 16 jaar hier

Topicstarter
Dag allen :w

Ik ben een beetje aan het frutselen met een site die tweetalig is. Ik wil het wel mooi/elegant doen dus niet overal en/nl hardcoden maar dus indirect de optie open houden om meer talen toe te voegen. Vooral zodat ik delen code eventueel later kan hergebruiken, of iig de ideeën erachter.
Nu heb ik wat zitten lezen en kwamen er een paar veelgebruikte DB-modellen naar voren:
1) Elke localizable string in een aparte tabel zetten met bijbehorende Locale en een common key. Die key sla je dan in de originele tabel op. Plus: Heel flexibele oplossing. Min: Heel veel queries en lastig te gebruiken in bijv een ORM
2) Kolommen voor elke vertaalde versie (dus name_nl, name_en). Plus: Enigszins makkelijk? Min: Totaal niet schaalbaar en imo helemaal niet netjes
3) Losse tabellen voor elke vertaalde versie (dus posts_nl, posts_en). Plus: Makkelijker? Als je je ORM wat modifiet kan het wellicht wel. Min: Niet heel storage-vriendelijk. Concurrency problemen. Lastig te onderhouden.

Nummer 1 leek mij dus het handigste in dit geval, dus ik ben een beetje gaan rondlezen hoe je dit goed met Entity Framework (ORM in dit geval) kan gebruiken. Nu kwam ik een (imo) erg elegante oplossing tegen die gebruik maakt van RealProxy's (in C#/.NET, http://stackoverflow.com/a/17002970). Het idee is dat die proxy de getters voor de resources onderschept, en de huidige waarde (de key) daarna opzoekt in de DB en de goede translated versie teruggeeft. Met enige caching voor de performance natuurlijk. Met ProxyAttribute kan je deze proxy automagisch bij new teruggeven waardoor je in het gebruik niet merkt dat er op de achtergrond dingen veranderen. Ik vind dit erg elegant, maar kan me heel goed voorstellen dat mensen dit te veel magie vinden. Wat dit bijvoorbeeld mogelijk maakt:
C#:
1
2
var foo = Db.Foos.First();
DoSomethingWith(foo.Name);

In dit geval is foo.Name dan vanzelf de vertaalde versie voor de current locale.

Maar ik merkte hier een paar problemen mee:
- De updatetracker van EF doet ook een get en ziet een andere waarde en gaat dan de DB updaten waarna de key overeenkomt met de vertaalde versie }:|
- Het updaten van vertaalde versies is nog steeds lastig

Hiervoor ben ik gaan prutsen met meerdere oplossingen/ideeën maar ik vond geen van allen echt mooi/elegant/goed werkend:
1) Voor 1: in de proxy calls vanuit EntityFramework assembly detecteren en aan die calls de originele key teruggeven bij een get. Voor 2: een normale set (niet vanuit EF) wordt ook onderschept en slaat de vertaalde versie op. De setter vanuit EF set de key in het veld.
Nadeel: Heel erg nasty om assemblies te detecteren. Niet flexibel met andere ORMs.
Voordeel: eenvoudig gebruik voor get/set, werkt allemaal vanzelf goed

2) Omdat de voordelen van 1 toch wel aantrekkelijk waren heb ik gekeken naar een andere manier om calls van EF te 'redirecten'. Ik heb toen iets bedankt om EF 'voor de gek te houden', het is niet heel elegant maar werkt wel. At runtime wordt de entity dynamisch geëxtend in een nieuwe class die xxxKey properties bevat voor alle te vertalen waarden (love TypeBuilder). In het normale gebruik wordt deze class gewoon als naar de BaseType gecast, maar EF herkent door het gebruik van reflection die nieuwe properties wél en gebruikt die dan tijdens het getten/setten. De proxy gebruikt de waarden uit die xxKey properties om de translated strings op te halen/op te slaan. De nieuwe class (FooLocalized) heeft dan dus Name en NameKey properties. De eerste wordt geïgnored in EF.
Dus voordelen: makkelijk in gebruik, weinig nadenken tijdens gebruik, databasemodel is direct goed/duidelijk zonder veel werk én ik heb het al werkend en het werkt wel echt goed (de taalafhandeling is compleet op de achtergrond, dus dat laat je focussen op de rest van de logica/het programma).
Nadeel: Het is nóg meer magie én de Fluent API voor EF is onbruikbaar geworden. Je kan eigenschappen van properties (stringlength, blabla) enkel met attributes setten, waardoor de flexibiliteit van EF een klein beetje verloren gaat (aangepaste many-to-many tabelnamen anyone? Meerdere relations naar dezelfde entity?). Dit omdat de Fluent API grotendeels met generics werkt en het type van de extended class enkel beschikbaar is on runtime. Ik heb geprobeerd alle eigenschappen geset op de originele class over te zetten naar de nieuwe class, maar dan moet je met reflection private readonly fields gaan aanpassen :X

3) Het hele idee van een proxy verlaten en gewoon functies in elke class maken die de localized versie ophalen. (GetLocalizedName oid)
Voordeel: Weinig magie
Nadeel: Heel veel handwerk bij elke class, veel herhalende code, veel logica in je models, heleboel werk weggooien.

Wat ik het mooiste zou vinden is om, zoals bij 2), gewoon enkel een attribuut te hoeven setten bij een property en dat dan de rest vanzelf gebeurt. Het is eenvoudig uitbreidbaar en zou eventueel ook als library in mijn andere projecten gebruikt kunnen worden. Maar ik kan nou niet echt een oplossing vinden voor de problemen. Wat vinden jullie? Is het überhaupt aan te raden om voor de proxies te gaan? Ben ik heel dom bezig? (vast) Of hebben jullie een ander idee om de problemen met de proxy op te lossen, of een alternatief voor de proxy?

2x Dell UP2716D | R9 7950X | 128GB RAM | 980 Pro 2TB x2 | RTX2070 Super
.oisyn: Windows is net zo slecht in commandline als Linux in GUI


Acties:
  • 0 Henk 'm!

  • Daos
  • Registratie: Oktober 2004
  • Niet online
Voor vaste teksten in een programma/(web)applicatie kan je gettext of varianten daarvan gebruiken. In je code zet je dan Engelse teksten die je door een vertaalfunctie haalt. Bv in php _('Hello World'). Een programma als poedit kan je code scannen op deze functies en daarmee kan je dan de vertalingen maken.

Of wil je echt dat de gebruikers de teksten in verschillende talen opgeven?

Acties:
  • 0 Henk 'm!

  • F.West98
  • Registratie: Juni 2009
  • Laatst online: 18:12

F.West98

Alweer 16 jaar hier

Topicstarter
Daos schreef op zaterdag 30 juli 2016 @ 16:04:
Voor vaste teksten in een programma/(web)applicatie kan je gettext of varianten daarvan gebruiken. In je code zet je dan Engelse teksten die je door een vertaalfunctie haalt. Bv in php _('Hello World'). Een programma als poedit kan je code scannen op deze functies en daarmee kan je dan de vertalingen maken.

Of wil je echt dat de gebruikers de teksten in verschillende talen opgeven?
Static content kan in .NET eenvoudig met ingebouwde Resources-class en dat gaat allemaal prima, maar die worden samen met het project gedeployed en gecompiled. Het gaat hier echt om dynamische inhoud die de gebruiker (ik) kan aanpassen zonder een redeploy te hoeven doen.

2x Dell UP2716D | R9 7950X | 128GB RAM | 980 Pro 2TB x2 | RTX2070 Super
.oisyn: Windows is net zo slecht in commandline als Linux in GUI


Acties:
  • 0 Henk 'm!

  • Daos
  • Registratie: Oktober 2004
  • Niet online
Voor dat gebruiken wij op het werk een vertalings-tabel met id, taalcode en tekst. Andere tabellen hebben dan bv dat id in het naam-veld staan.

Voor ophalen hebben wij een aparte functie, maar het zou ook met een join kunnen in sql op tabel.naam = vertaling.id AND vertaling.taalcode="nl_NL". Hoe het moet in een orm weet ik niet.

Dit is jouw optie 1, maar dan zonder de proxy.

Acties:
  • 0 Henk 'm!

  • F.West98
  • Registratie: Juni 2009
  • Laatst online: 18:12

F.West98

Alweer 16 jaar hier

Topicstarter
Daos schreef op zaterdag 30 juli 2016 @ 16:33:
Voor dat gebruiken wij op het werk een vertalings-tabel met id, taalcode en tekst. Andere tabellen hebben dan bv dat id in het naam-veld staan.

Voor ophalen hebben wij een aparte functie, maar het zou ook met een join kunnen in sql op tabel.naam = vertaling.id AND vertaling.taalcode="nl_NL". Hoe het moet in een orm weet ik niet.

Dit is jouw optie 1, maar dan zonder de proxy.
Ja klopt. Alleen is de join elke keer vrij intensief als je veel localizable kolommen hebt, dus daarom ook caching. Blijft alleen in een ORM vrij omslachtig om het te doen, je moet heel veel code overal 'dupliceren', overal moet dezelfde selectielogica opnieuw gedaan worden. Losse functies maken kan ook, maar zorgt dan dus weer voor meer logica in je models, of een minder duidelijk model.

2x Dell UP2716D | R9 7950X | 128GB RAM | 980 Pro 2TB x2 | RTX2070 Super
.oisyn: Windows is net zo slecht in commandline als Linux in GUI


Acties:
  • 0 Henk 'm!

  • Daos
  • Registratie: Oktober 2004
  • Niet online
Weet je al dat je caching nodig hebt? Een paar indexen in de vertalings-tabel helpt al een stuk. Heb je wel eens gehoord van 'Premature Optimization'?
Pagina: 1