2009. június 4., csütörtök

Lokalizáció, globalizáció és kultúrák harca - Part 1

What is Globalization/Localization in Microsoft Framework perspective for building multi-cultural applications? Answer: If u declare a variable locally, it's localization. If you declare it globally, its globalization.
Adnan Masood - "Some interesting .NET interview answers"

Hát, valami ilyesmi, ha nem is pont ez.
A globalizáció, lokalizáció, internacionalizáció* egy olyan témakör, amibe könnyen bele lehet keveredni - pedig nincs benne semmi nehézség, csak a megértése feltételezi pár alapfogalom, illetve a terminológia ismeretét. Az viszont nem jön magától, úgyhogy ússzunk is gyorsan néhány tempót a tudás óceánjában.

*A internationalization-t szokták i18n-nek rövidíteni. Hogy miért pont így? Mert i-vel kezdődik, n-re végződik, és közte van még 18 betű. Jó, nem?

Első körben, miről is beszélünk? A fenti három fogalomra mindenkinek megvan a maga egzakt és pontos definíciója, az enyém:

  • Lokalizáció: egy szoftvertermék nyelvi és kultúrális testre szabása egy adott célcsoport számára.
    Az első félreértés az szokott lenni, hogy ez fordítást jelent: valóban talán a legfontosabb (de mindenképp a "leglátványosabb") a nyelvi lokalizáció, de emellett szintén nagyon fontos az idő-, dátum- és számformátum, illetve a sorbarendezés helyi szokásokhoz való igazítása, tágabb értelemben pedig még rengeteg minden, kezdve a helyi jogszabályoknak való megfeleléstől odaáig, hogy a mi kultúránkban egy teljesen ártalmatlan, hétköznapi ikon esetleg közröhely tárgya lesz, vagy épp óriási felháborodást kelt néhány ezer kilométerrel arrébb.
  • Globalizáció, Internacionalizáció: az én szótáramban ezek szinonímák. Vannak, akiékben nem, ők a általában a globalizációt az internacionalizáció valami magasztosabb, mindent átható, filozófiai magasságokba emelkedő verziójának tekintik - nekem mindegy, a lényeg kb. mindként esetben ugyanaz: lehetővé, sőt, ha lehet, habkönnyűvé tenni a lokalizációt. Magyarán eleve úgy tervezni (vagy ha erről már lekéstünk: átalakítani) egy szoftvert, hogy később könnyen lehessen lokalizálni. Kedzve attól, hogy nem hardkódolok be literálokat az alkalmazás kódjába, azon keresztül, hogy a GUI tervezésekor gondolok arra, hogy a menüpontok németül se lógjanak háromszorosan egymásra, odáig, hogy kinyomozom, hogy az alkalmazásom háttérszíne bizonyos kultúrákban nem pont a dzsihád színe-e esetleg.

Most hogy így az alapfogalmakkal megismerkedtünk, nézzük mit ad nekünk a .NET ezen a téren.
Nem is mit, hanem miket: ad minden alkalmazáshoz két tulajdonságot, a CurrentCulture-t és a CurrentUICulture-t. Ezeket az aktuális thread objektumon keresztül érhetjük el:

var currentThread = Thread.CurrentThread;

var currentCulture = currentThread .CurrentCulture;
var currentUICulture = currentThread .CurrentUICulture;
[Itt mindjárt meg is buktam a hazugságommal, miszerint ezek az alkalmazásunk tulajdonságai lennének, hiszen jól látszik, hogy igazából szálhoz kötődnek.]

Na de mi a különbség? Miért kell kettő? Ha azt tippeljük, hogy azért, mert két különböző dolgra vonatkoznak, jól tippelünk.
Az elnevezésük talán nem a legszerencsésebb, mert azt sugallja, hogy a "sima" CurrentCulture általánosabb, de ennek a sugallatnak ne dőljünk be. Jegyezzük meg úgy, hogy az, aminek a nevében benne van a "UI", nem túl meglepő módon az UI nyelvének beállítására szolgál. Magyarán, hogy milyen nyelvül beszéljen a programunk, vagy hogy épp a Framework milyen nyelvű exceptiont dobjon. Ha nagyon technikai akarnék lenni, azt mondanám: a ResourceManager egy adott resource feloldásakor a CurrentUICulture alapján dönti el, hogy adott kulcshoz melyik nyelvi verziót szolgáltassa.
A másik, "sima" culture pedig az befolyásolja, hogy a idő- és dátum értékek, számok, pénzösszegek hogyan formázódnak ki a kimenetre (pl. egy .ToString() hívás során), illetve hogy visszairányban milyen formátumból parsolhatók fel.

Honnan jönnek ezek az értékek? Hát jöhetnek egyrészt onnan, hogy mi jól beseteljük őket - mind a CurrentCulture, mind a CurrentUICulture írható, kódból beállítható. Na jó, de honnan jönnek a defaultok?

  • A CurrentCulture (emlékszünk: dátum és számformázás) defaultja az lesz, amit a Windows vezérlőpultján a Regional and Language Options alatt beállítunk. Itt akár részleteiben testre tudjuk szabni, hogy mi tizedesvesszőt, tizedespontot, vagy akár "tizedes A-betűt" akarunk használni. Kipróbálhatjuk: ha beállítjuk, hogy a mi kultúránkban a decimal symbol az "A", a .NET híven fogja követni a hülyeséget, és a "var pi = 3.1415; string s = pi.ToString();" után az s értéke "3A1415" lesz.
  • Hát a CurrentUICulture (emlékszünk: a felület nyelve) defaultját hol lehet beállítani? Sehol. Az a Windowsunk nyelvét fogja alapbeállításnak venni, tehát egy magyar Windowsnál magyart, német Windowsnál a németet. Hacsak nincs MUI (Multilingual User Interface) installációnk, mert akkor meg azt, amit épp beállítottunk Windows nyelvnek.


A következő részben bejön a képbe az ASP.NET, hogy tovább zagyválja a default beállításokat, majd specifikus, illetve neutrális CultureInfókkal zsonglőrködünk, invariant módon formázuk.
Izgalmas lesz, stay tuned!

2009. június 2., kedd

LINQ to SQL vs. Tranzakciók

A .NET Framework 2.0-ás verziójától (azaz elég régóta) két lehetőségünk van tranzakciók kezelésére: az ADO.NET-féle, IDbTransaction implementációkra alapuló megoldás, illetve a System.Transactions.dll-ben lakó, hasonló nevű névtér alatt található osztályokra támaszkodó verzió (ügyfélkódban leginkább a TransactionScope osztállyal talákozhattunk).
Igazából nem is kétféle lehetőségünk van, hanem három, mert ott volt még az Enterprise Services-féle megoldás is, de most se ebbe, se az előbbi kettő működésébe nem mennék bele, mert a probléma, ami e post írására sarkallt, az az, hogy ezeket a tranzakció-kezelési megoldásokat hogy lehet a LINQ to SQL-lel összeházasítani.

A rövid válasz: szerencsére könnyen. :)
A kicsit hosszabb válasz az, hogy mind az ADO.NET-es, mind a System.Transactions-ös tranzakciók támogatottak, de mindkettő másképp. LINQ to SQL-ről lévén szó, nem nagy meglepetés, hogy a DataContext osztály körül fogunk pörögni, hiszen ez az a "központi" oszály, aminek segítségével az adatforrásunkból lekérdezve, vagy a SubmitChanges() által oda a változtatásainkat visszaírva a háttérben SQL műveleteket tudunk végrehajtani - márpedig mi pont ezeket a műveleteket szeretnénk tranzakcionálisan végrehajtani.

Az első esetben a DataContext osztályunk Transaction property-jét kell beállítani arra az IDbTransaction példányra, amiben részt szeretnénk venni:

IDbTransaction transaction; // ez jön valahonnan...

using (var db = new MyDataContext())
{
    db.Transaction = transaction;

    // DB mókolás...
    db.SubmitChanges();

    // további változtatások...
    db.SubmitChanges();
}

// Ne felejtsünk el kommitálni, amikor kell...
transaction.Commit();
Ebben az esetben, ha a Transaction property-t valamilyen nem-null értékre állítjuk, a LINQ to SQL megteszi azt a szívességet, hogy nem zárja le a DataContext osztályunkhoz tartozó connection-t.

A másik lehetőség a System.Transactions.TransactionScope osztály használata.
Ebben az esetben még egyszerűbb dolgunk van, ugyanis semmit nem kell tennünk (legalábbis semmi olyat, amit egy "normál" adatrétegben ne kéne amúgy is). A LINQ to SQL érzékelni fogja, hogy egy TransactionScope-ban lett hívva az API valamely (teszemazt a SubmitChanges()) metódusa, és abban a scope-ban fog végrehajtódni. Az adatbázis-kapcsolatunk ebben az esetben is nyitva marad.

Érdemes még tudni, hogy a LINQ to SQL akkor is tranzakcionálisan perzisztálja le a változtatásainkat, ha nem adunk meg explicit tranzakciót. Ilyen esetben (tehát ha a Transaciton property null, TransactionScope pedig nincs), akkor a LINQ to SQL maga nyit egy ADO.NET tranzakciót, és azon belül próbálja végrehajtani a változtatásainkat.