HAVIT Knowledge Base

Vývoj webových aplikací, .NET, SQL, návrh
Welcome to HAVIT Knowledge Base Sign in | Join | Help
-
Home Články Forums Obrázky Soubory

ASP.NET

Vývoj webových aplikací ASP.NET

Lokalizace snadno a rychle - explicitní lokalizace

Předesílám, že se v tomto článku budu zabývat výhradně explicitní lokalizací webových projektů, tedy přímým odkazováním na resources pomocí <%$ Resources: ... %>, popř. metod GetLocalResourceObject() nebo GetGlobalResourceObject(). Osobně mám tuto metodu radši, protože mám přesně pod explicitní kontrolou každý bajt, který chci lokalizovat.

Co všechno je tedy pro úspěšnou implementaci lokalizace udělat?

  1. Vytvořit resources - .resx soubory s lokalizovanými texty, popř. i jinými objekty.
  2. Přidat do stránek/kódu odkazy na resources tam, kde chceme lokalizaci.
  3. Vytvoření lokalizovaných verzí .resx souborů.
  4. Zajistit nastavení a případně i přepínání CurrentUICulture, popř. i CurrentCulture.

Vytvoření primárních resources - .resx souborů

Předpokládám běžnou práci ve Visual Studiu nebo Web Developer Express, nebudu se tedy zabývat takovými věcmi jako je resgen. VS/WDE přímo podporují přehledné vytváření a editaci .resx souborů.

Důležité je, že rozlišujeme dva typy resources:

  1. globální resources
    • data v nich jsou přístupná ze všech míst webové aplikace,
    • jsou umístěny ve složce ~/App_GlobalResources
    • pojmenovávají se MojeJmeno.resx, např. ~/App_GlobalResources/Glossary.resx
    • může jich být libovolné množství, nicméně je rozumné používat jen několik logických celků, např. Navigation.resx, Glossary.resx, atp.
  2. lokální resources
    1. data v nich jsou přímo přístupná jen ze stránky/controlu/..., které se týkají,
    2. umísťují se do složky App_LocalResources, která je podsložkou sloužky, kde je stránka/control,
    3. pojmenovávají se MojeStranka.aspx.resx, popř. MujControl.ascx.resx, atp.; například pro stránku ~/admin/stranka.aspx bude lokální resource file ~/admin/App_LocalResources/stranka.aspx.resx

Resource-file vytvoříme snadno, ve VS/WDE prostě vytvoříme příslušnou složku a do ní přes Add přidáme položku typu Resource. Resource soubor .resx je ve skutečnosti XML souborem, který je přibuildován do assembly, pozadí však nechme stranou.

Po otevření .resx souboru se nám standardně zobrazí grid pro editaci/přidávání objektů typu string. Resource nemusí být jen typu string, ale například i obrázky, ikony, zvuky, soubory nebo obecné objekty, nicméně pro běžnou lokalizaci si naprosto vystačíme s typem String, protože ve webových aplikací i u obrázků a ostatních objektů obvykle "lokalizujeme" jen odkaz na příslušný soubor na disku (ImageUrl = "vlajka_cz.gif") spíše než samotné soubory.

V praxi .resx záznamy v .resx souborech vznikají současně s prvním odkazem na ně, přistupme tedy k použití ve stránce/kódu.

Použití resources ve stránce/kódu

Explicitní použití resource ve stránce je primitivní, v tagu controlu, všude, kde lze přiřadit string, stačí zapsat odkaz

  • <%$ Resources: ResourceName %> pro lokální resources
  • <%$ Resources: FileNameBezPripony, ResourceName %> pro globální resources

Například tedy

<asp:Localize Text="<%$ Resources: UvodniText %>" ... runat="server" />
<asp:Label Text="<%$ Resources: Glossary, Telefon %>" ... runat="server" />
<asp:Image ImageUrl="<%$ Resources: Images, Vlajka %>" ... runat="server" />

Pozor, že nejde použít <%$ ... %> mimo serverové tagy, pro takový účel slouží právě nový control Localize, který není ničím jiným než Literalem s lepší design-time podporou.

Pro použití resources v kódu je několik možností

  1. Globální resources se zrcadlí do strong-typed properties ve třídách namespace Resources. Například Resources.Glossary.Telefon nebo Resources.Images.Vlajka. Tento wrapper generuje ASP.NET v rámci web-site a je tak přístupný pouze v rámci web-site a nelze na něj odkazovat například z jiných assembly.
  2. Pro obecný přístup ke globálním a lokálním resources slouží metody třídy HttpContext
public static object GetGlobalResourceObject(string classKey, string resourceKey);
public static object GetGlobalResourceObject(string classKey, string resourceKey, CultureInfo culture);
public static object GetLocalResourceObject(string virtualPath, string resourceKey);
public static object GetLocalResourceObject(string virtualPath, string resourceKey, CultureInfo culture);
// typicky třeba maily
mail.Subject = (string)HttpContext.GetGlobalResourceObject("MailTemplates", "MyMailSubject");
// nebo ve stránce
MessageLb.Text = (string)GetLocalResourceObject("Neuspech");

Metody jsou mimo třídy HttpContext přístupné i v třídě TemplateControl a jejích potomcích, tedy např. i Page.
Pro local resources bohužel ASP.NET žádný wrapper negeneruje a z kódu na ně přistupujeme  rovnou přes GetLocalResourceObject().

Pro přístup k resources můžeme použít samozřejmě i standardní techniky přes ResourceManager atp., ale ve webových aplikacích to není běžné.

Co jsme dosud udělali nám samo o sobě už bude fungovat a zobrazovat, nicméně stále jsme jen u jednoho primárního jazyka. Pro přidání dalších jazyků a případně i možnost jejich přepínání musíme provést ještě dva kroky.

Vytvoření resources (.resx souborů) pro další jazyky

Vytvoření resources pro další jazyky je velmi primitivní záležitostí. Stačí vzít .resx soubor primárního jazyka a vytvořit jeho kopii se jménem Soubor.jazyk.resx, například tedy ze souboru ~/App_GlobalResources/Glossary.resx vytvoříme kopii ~/App_GlobalResources/Glossary.en.resx (pro angličtinu), nebo ze souboru ~/App_LocalResources/Login.aspx.resx vytvoříme kopii ~/App_LocalResources/Login.aspx.de.resx (pro němčinu).

...a nyní nezbývá než hodnoty v novém souboru přeložit. Pokud navíc nainstalujeme překladateli WDE a naučíme ho editovat přímo .resx soubory, krása. Existují dokonce mnohé šikovné utilitky pro práci s .resx soubory, aby nebylo nutné instalovat něco tak velkého jako WDE:

Volba jazyka a přepínání jazyků

Jakou jazykovou verzi odešle ASP.NET na klienta se řídí hodnotou Thread.CurrentThread.CurrentUICulture, zároveň se doporučuje volit i Thread.CurrentThread.CurrentCulture, protože tím se řídí formát čísel, času, dat, měny, atp.

Automatická volba dle požadavku prohlížeče

První, co můžeme chtít, je, aby ASP.NET použilo jazykový požadavek prohlížeče, který ho posílá v HTTP requestu. Česká prostředí tedy rovnou uvidí české stránky, anglická anglické, atp. a nic nemusíme ani přepínat. Prohlížeče mají jazyk přednastaven od instalace a například v Internet Exploreru je možné ho měnit přes Nástroje ~ Možnosti Internetu ~ Obecné ~ Jazyky.

Jediné, co pro implementaci tohoto postupu potřebujeme udělat, je ve web.config nastavit hodnoty elementu <globalization>, atributy uiCulture, popř. culture:

<globalization
   requestEncoding="utf-8"
   responseEncoding="utf-8"
   culture="auto:cs-CZ"
   uiCulture="auto:cs-CZ"
/>

 Volba auto říká, že se má použít požadavek prohlížeče, za dvojtečku lze volitelně umístit primární nastavení, které se má použít, pokud není požadavek prohlížeče realizovatelný, resp. pokud prohlížeč požadavek neudal. Pro úplnost uvádím i volby encoding, ty však nejsou předmětem tohoto článku.

Explicitní volba jazyka uživatelem

Dále můžeme chtít, aby si uživatel sám mohl přepnou jazyk, nezávisle na požadavku prohlížeče. Je jasné, že budeme muset přepnout Thread.CurrentThread.CurrentUICulture popř. i CurrentCulture. Zvolený jazyk si budeme ukládat třeba do cookie (použitelná je i session) a je potřeba ho přepnout pro každý request, a to co nejdříve. Buď můžeme udělat override prázdné virtuální metody Page.InitializeCulture(), pokud máme připravenou bázovou třídu pro všechny stránky, anebo změnu provádět rovnou v události Application_BeginRequest, v Global.asax.cs:

private void Application_BeginRequest()
{
   HttpCookie cookie = Request.Cookies["Culture"];
   if (cookie != null)
   {
      CultureInfo culture = new CultureInfo(cookie.Value);
      Thread.CurrentThread.CurrentCulture = culture;
      Thread.CurrentThread.CurrentUICulture = culture;
   }
}

a pro vlastní přepínání si uděláme třeba stránku Language.aspx:

protected override void OnInit(EventArgs e)
{
   string culture = Request.QueryString["culture"];
   if ((culture != null) && (culture.Length > 0))
   {
      HttpCookie cookie = new HttpCookie("Culture");
      cookie.Value = culture;
      Response.Cookies.Add(cookie);
   }
   string returnUrl = Request.QueryString["returnUrl"];
   if ((returnUrl != null) && (returnUrl.Length > 0))
   {
      Response.Redirect(returnUrl);
   }
   else
   {
      Response.Redirect("~/");
   }
   base.OnInit(e);
}

odkaz pro přepnutí stránky, pak může být třeba

<a href="language.aspx?culture=en-US">en</a> nebo s ReturnUrl stejně jako login-page.

Související články

V příloze najdete i PowerPoint prezentaci k tématu.

Published 4. května 2006 9:23 by Robert Haken
Attachment(s): Lokalizace webových aplikací.ppt

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

 

Vašek Nauš said:

Jen pro úplnost. Pokud chcete ukládat data do Session, musíte v Global.asax implementovat metodu Application_PreRequestHandlerExecute. Alespoň já to tak řešil, protože v metodě Application_BeginRequest ještě není definovaná Session. Dále jsem musel předávat do Thread.CurrentThread.CurrentCulture instanci System.Globalization.CultureInfo.CreateSpecificCulture(ci.Name), protože třeba můj firefox, předává v UserLanguages pouze "cs". Vašek PS: děkuji za podnětný článek Celý kód pak vypadá takto: void Application_PreRequestHandlerExecute(object sender, EventArgs e) { HttpContext http = HttpContext.Current; System.Globalization.CultureInfo ci; if (http.Session == null) { ci = new System.Globalization.CultureInfo("en-US"); } else { if (http.Session["language"] == null) { //musim nastavit do sesny jazyk - z klienta if ((http.Request.UserLanguages != null) && (http.Request.UserLanguages.Length > 0)) ci = new System.Globalization.CultureInfo(http.Request.UserLanguages[0]); else ci = new System.Globalization.CultureInfo("en-US"); http.Session["language"] = ci; } else ci = (System.Globalization.CultureInfo)http.Session["language"]; } System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.CreateSpecificCulture(ci.Name); System.Threading.Thread.CurrentThread.CurrentUICulture = System.Globalization.CultureInfo.CreateSpecificCulture(ci.Name); }
října 24, 2006 19:38
 

Robert Haken said:

Díky za doplnění informace o Session a Application_PreRequestHandlerExecute, i když zrovna session obecně nepovažuji za příliš vhodné uložiště pro tuto informaci, to už kdyžtak Profile.

S tím CreateSpecificCulture to podle mě není potřeba, pokud necháte vyřídit požadavek volbou "auto" z web.configu (zvládne to i "cs"). Explicitní volba z kódu je přeci potřeba jen po explicitním přepnutí uživatelem a tam není problém pro přepínání použít úplné "cs-CZ".

října 24, 2006 23:17
 

Vašek Nauš said:

Předně se omlouvám za kód v předchozím příspěvku, který je neformátovaný a tudíž nepřehledný. Profily ještě nemám příliš nastudované, takže mne to nenapadlo a asi to takto předělám. Sice by to v cookies nezabralo příliš místa, ale obecně se snažím minimalizovat věci, které se přenášejí na klienta a pokud je to možné, nacpu to někam na serveru - nejlépe DB. Mám všechno pod kontrolou a nemusím mít strach, že se mi vrátí něco, co nechci, nečekám. Ohledně toho CreateSpecificCulture. Ve web.cfg volbu "default" jazyka nemám, protože to je v mém případě zbytečné. Pokud neexistuje Session, vytvářím jako default en-US. Je mi jasné, že hned vyvstane argument, že pro změnu výchozího jazyka je třeba editovat zdrojáky, ale změna nenastane - tím jsem si jist. Proto nedokáži posoudit, jestli je chybou, že to tam není. Každopádně mne to vyhazovalo výjimku "Culture 'cs' is a neutral culture. It cannot be used in formatting and parsing and therefore cannot be set as the thread's current culture.", protože se dívám na jazykové nastavení prohlížeče, které beru z Request.UserLanguages a tam mi lišák dává pouze "cs"
října 25, 2006 0:28
 

Robert Haken said:

Výhoda cookie je právě v tom, že umožní zapamatování volby jazyka přes více session, na další návštěvy stejného uživatele. Stejný efekt dostaneme i s profily, pokud je implementujeme.

října 25, 2006 7:59
 

Libor Tyl said:

Inspiroval jsem se vaším přehledným článkem o lokalizaci, mám jen drobnou připomínku.

V Language.aspx se v metodě OnInit na závěr volá base.OnLoad(e).

Je to metoda OnInit, tak by tam asi mělo být na vhodnějším místě base.OnInit(e).

Po předchozích redirectech se tento příkaz stejně neprovede.

listopadu 29, 2009 16:13
 

Robert Haken said:

to Libor Tyl: Díky za upozornění, opravil jsem. I když jak říkáte, v tom místě je tam to volání zbytečné, protože se neuplatní, je to jen takové "z principu". Obecně se v těchto případech base metoda volává na začátku svého rozšiřujícího override, ale zde je to opravdu úplně jedno - není moc šance, že by tam měl někdo obsluhu události Init, kterou by to neobsloužilo (base jen volá případné obsluhy události, sám nic nedělá).

listopadu 29, 2009 17:54

What do you think?

(required) 
(optional)
(required) 
Enter the code you see below

Submit