V tomto článku si ukážeme, jak komplexním mechanismem lokalizace aplikací je vybaven .NET a jakým způsobem tedy můžeme měnit i jazyk textů naší ASP.NET aplikace. Využijeme přitom takzvaný Resource Manager a naučíme se také nastavit jazyk aplikace automaticky dle preferovaného jazyka klienta.

Na začátku si řekněme, že dále popisovaný způsob pomocí Resource Manageru, nám umožní snadno lokalizovat pouze texty, které mám v aplikaci takříkajíc natvrdo (různé texty tlačítek, popisků), pokud bychom chtěli lokalizovat texty z databází nebo XML zdrojů, Resource Manager nám nepomůže – zde je potřeba rozšířit datový zdroj tak, aby byl schopen poskytnout texty ve zvoleném jazyku.

Nejprve si řekněme pár slov o tom, jak .net Framework sám o sobě podporuje lokalizace aplikací – podpora je zde velmi dobrá a to díky třídám zahrnutým v prostoru názvů System.Globalization. Pro základní přizpůsobení jazyka, tj. jak aplikace bude s uživatelem mluvit, nás bude pro začátek zajímat třída CultureInfo, kde souhrn vlastností jakým jazykem aplikace mluví a jaké používá národní zvyklosti je popsán jako tzv. Culture. Určitou podmnožinou, která při daném Culture respektuje národní odlišnosti zahrnuje třída RegionInfo, ale tu pro naši potřebu jazykového přizpůsobení můžeme prozatím ponechat stranou.

Jazyk prostředí v jakém nám aplikace běží, tzv. Culture, a prostředí, v jakém zobrazuje uživatelské rozhraní (UICulture) je možné nastavit několika způsoby. Zde je dobré si říci, že pro určení národního prostředí používáme jednotné identifikátory – jejich seznam lze nalézt také v MSDN. Pro celou aplikaci toto můžeme definovat v souboru Web.config:

<configuration>
  <system.web>
    <globalization
      fileEncoding=“utf-8″
      requestEncoding=“utf-8″
      responseEncoding=“utf-8″
      culture=“pl-PL“
      uiCulture=“pl-PL“
    />
  </system.web>
</configuration>

Pro jednotlivé stránky je možné použít nastavení v direktivě @Page:

<%@Page Culture=“fr-FR“ UICulture=“fr-FR“ ResponseEncoding=“utf-8″%>

Nastavení je možné měnit i za běhu aplikace – importujeme si prostor názvů System.Globalization, ve kterém se nachází třída CultureInfo, abychom tak mohli měnit vlastnosti aktuálního vlákna aplikace (CurrentThread):

System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(„de“);
System.Threading.Thread.CurrentThread.CurrentCulture = new CultureInfo(„de-DE“);

Později si ukážeme, jak právě tímto způsobem nastavovat jazyk aplikace za běhu automaticky. Zde bych jen doplnil, že pokud chceme, aby v naší aplikaci naším jazykem mluvily i vestavěné součásti .net Frameworku (například kalendář), je potřeba stáhnout jazykový balíček (LanguagePack) daného jazyka (pokud je k dispozici).

Nyní víme, jak aplikaci říct, jakým jazykem má mluvit. Prostá změna Culture a UICulture nám ovšem nepomůže k tomu, aby se text našeho tlačítka Send změnil například na Odoslať – texty jednotlivých jazykových verzí je potřeba mít připravené. Aby je aplikace měla jednoduše „po ruce“, existuje způsob, jak tyto texty naskládat do zabalených knihoven (tzv. satellite assemblies), ve kterých si je aplikace dle potřeby najde.

První co musíme udělat je připravit si tedy text výchozí jazykové verze a dalších jazykových verzí, které chceme podporovat. Zde je ukázka výchozí a slovenské verze souboru se zdroji textů (povšimněte si také názvu souboru s výchozím jazykem a slovenským jazykem:

Application.Languages.txt:

FooterPrintText = Print
FooterPrintToolTip = Print page (CTRL+P)

Application.Languages.sk-SK.txt

FooterPrintText = Tlač
FooterPrintToolTip = Vytlačiť stránku (CTRL-P)

Jak vidíme, každý text je jednoznačně určen klíčem (např. FooterPrintText) – tyto klíče musejí být pro všechny jazykové verze shodné, protože tento klíč je pak v aplikaci použit pro určení, který text má ResourceManager vytáhnout. Pokud manažer daný klíč nenalezne v knihovně daného jazyka, pokusí se jej najít ve výchozí knihovně – nemusíme proto při přidávání nových funkcionalit nutně překládat úplně všechny jazykové verze, pouze nám nové ještě nepřeložené funkce budou mluvit výchozím jazykem.

Názvy souborů se zdroji textů můžou být libovolné, ale já vám opravdu dobře doporučuji použít zde uvedený vzor – budete mít jednotlivé zdrojové soubory přehledné a snadno z nich zkompilujete požadované knihovny. Také při tvorbě názvů klíčů jednotlivých textů se hodí držet se názvu stránky nebo user controlu, kde se text používá, dále název funkce prvku a případně další rozlišení (jako zde v příkladu, kde jde o text odkazu a doplňkový popisek do atributu title).

Vyrobit z textového souboru jazykovou assembly není až tak jednoduché – z čistého textového souboru je možné vyrobit tzv. resource soubor. Ačkoliv by bylo možné v aplikaci použít ResourceManager s využitím resource souborů, doporučuji tyto resource soubory dále zkompilovat do knihoven (*.dll). Použití resource souborů se mi v aplikaci jeví jako pomalejší a navíc dochází k trvalému otevření resource souborů, jakmile je ResourceManager poprvé použije – takto by bylo velmi složité například aktualizovat tyto soubory na webovém serveru. Dokud by nedošlo k restartu webové aplikace, resource soubory by nebylo možné přepsat. Raději proto ve dvou krocích vytvoříme z textového zdroje soubor resource a z něj poté knihovnu.

Ke kompilaci jazykové knihovny potřebujeme nástroje ResGen.exe a Al.exe. Zatímco Al.exe je součástí standardní instalace .net Frameworku, Resgen.exe je součástí balíčku SDK – ten je tedy potřeba stáhnout zvlášť. Soubor resource dané jazykové verze vytvoříme pomocí ResGen.exe jednoduše:

ResGen.exe Soubor.sk-SK.txt Soubor.sk-SK.resources

Pro výchozí jazyk prostě označení jazykové verze vypustíme, parametry tedy budou Soubor.txt Soubor.resources

Resource soubor je pak možné „složit“ do knihovny pomocí nástroje Al.exe:

Al.exe /nologo /embed:Soubor.sk-SK.resources,Soubor.sk-SK.resources /out:Soubor.resources.dll /c:sk-SK

Povšimněte si, že v názvu knihovny již označení jazyka není – a to je trochu problém, jazyk totiž bude rozlišován názvem adresáře, do kterého je potřeba knihovnu uložit. Protože je toto poněkud mechanická činnost, vytvořil jsem si dávkový soubor, který vezme zadaný textový soubor a případně i jazyk. Pokud nezadáme jazyk, jde o výchozí jazyk. Pokud jazyk zadáme, je zvlášť vytvořen adresář daného jazyka a do něj je vytvořena patřičná jazyková knihovna:

@Echo Off
Set Path=C:\PROGRA~1\MICROS~2.NET\SDK\v1.1\Bin\;%PATH%
If %2XXX == XXX GoTo NoLang
  ResGen.exe %1.%2.txt %1.%2.resources
  Echo.
  If Not Exist %2 MkDir %2
  Al.exe /nologo /embed:%1.%2.resources,%1.%2.resources /out:%2/%1.resources.dll /c:%2
Goto End
:NoLang
  ResGen.exe %1.txt %1.resources
  Echo.
  Al.exe /nologo /embed:%1.resources,%1.resources /out:%1.dll
:End

V dávce si dočasně nastavíme cestu k .net SDK, aby bylo možné snadno použít nástroj ResGen.exe. Dále zjistíme, byl-li zadán jako druhý parametr identifikátor jazyka. Pokud ne, prostě se nechá vytvořit resource soubor a zkompiluje se výchozí assembly. Pokud jazyk byl zadán, vytvoří se pro něj resource soubor, dále se ověří, zda existuje adresář daného jazyka – pokud ne, vytvoří se. Nakonec se do daného jazykového podadresáře vytvoří jazyková assembly. Pokud bychom například vytvářeli výchozí a slovenskou verzi, výsledkem bude tato struktura souborů a adresářů:

Soubor.txt
Soubor.sk-SK.txt
Soubor.resource
Soubor.sk-SK.resource
Soubor.dll
[sk-SK]
    Soubor.dll

Soubory knihoven ukládáme ve webových aplikacích do složky Bin – musíme tam tedy nakopírovat soubor Soubor.dll také složku sk-SK i s jejím souborem Soubor.dll. A protože ani tato operace není nijak záživná, připravil jsem si další dávku, která vezme všechny zadané zdrojové soubory s texty a vytvoří potřebné adresáře a zkompiluje do nich potřebné knihovny:

@Echo Off
Call MkLanguage.bat Application.Languages
Call MkLanguage.bat Application.Languages cs-CZ
Call MkLanguage.bat Application.Languages sk-SK
Call MkLanguage.bat Application.Languages en-US
Call MkLanguage.bat Application.Languages en-GB
Call MkLanguage.bat Application.Languages de-DE
Echo.
Pause

Další podporované jazyky stačí prostě pouze připsat do této dávky. Jakmile provedeme nějaké změny ve zdrojových souborech, spustíme tuto dávku a vytvořenou výchozí knihovnu i s podadresáři daných jazyků přeneseme do složky Bin naší virtuální aplikace na serveru.

V tuto chvíli máme připraveny knihovny s texty a zbývá nám poslední – v naší aplikaci vytvořit ResourceManager, který nám bude texty daného jazyka zpřístupňovat. ResourceManager se hodí vytvořit jako statický objekt (ideálně dostupný v celé aplikaci umístěním do Global.asax), pro názornost si ukážeme použití samostatně v jednoduché stránce:

public static readonly System.Resources.ResourceManager LocalizationManager = new System.Resources.ResourceManager(„Application.Languages“,System.Reflection.Assembly.Load(„Application.Languages“));
private void Page_Load(Object sender, EventArgs e)
{
  Response.Write(LocalizationManager.GetString(„FooterPrintText“);
}

Konstruktorem ResourceManageru si vytvoříme nový manažer postavený na assembly s výchozím jazykem (Application.Languages.dll). Text odpovídajícího jazyka potom získáme metodou GetString(). Jenom pro úplnost si ukažme, jakými způsoby je možné do stránky zapracovat jazykově závislý text.

Přiřazením v programu za běhu:

Label1.Text = myManager.GetString(„Text1“);

Nabindováním (např. v šablonách vykazovacích prvků jako Repeater, DataGrid…):

<%# myManager.GetString(„Text1“) %>

Přímým renderováním (jako Response.Write()):

<%=myManager.GetString(„Text1“) %>

Zde se hodí poznamenat, že nově chystané ASP.NET 2.0 zavádí další způsob, navíc VS 2005 automaticky vygeneruje třídu pro přístup k resources, takže místo vytváření objektu ResourceManager a používání metody GetString() stačí napsat Resources.Text1:

<asp:Label Runat=“Server“ meta:resourcekey=“Text1″ />

V tento okamžik nám již aplikace díky připraveným textům mluví jazykem, který odpovídá již dříve zmíněnému Culture. Jako malý bonus si proto ještě ukážeme, jak zajistit, aby se Culture nastavilo automaticky podle preferovaného jazyka klienta. Preferovaný jazyk je získáme z kolekce Request.UserLanguages a pokusíme se z něj vytvořit odpovídající Culture. Pokud se to zdaří, nastavíme na něj aktuální Culture i UICulture. Prohlédněte si ukázku, ve které prostě nechávám zobrazit pár textů určených pro odkazy (pro vyzkoušení je potřeba ve vašem prohlížeči prostřídat několik preferovaných jazyků na prvním místě – češtinu, slovenštinu, angličtinu).

String _myLanguage = Request[„Language“];
try
{
  if (_myLanguage != null && _myLanguage.Length > 1)
    System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.CreateSpecificCulture(_myLanguage);
  else
  {
  // převezmi první jazyk uživatele a zahoď případné doplňkové informace za středníkem
    String _userLanguage = Request.UserLanguages[0];
    if (_userLanguage != null && _userLanguage.Length > 1)
    {
      Int32 strPos = _userLanguage.IndexOf(„;“);
      if (strPos != -1)
      _userLanguage = _userLanguage.Substring(0,strPos);
      // pokud i po odříznutí části za středníkem něco zbude, pokusíme se nastavit takové Culture
      if (_userLanguage.Length > 1)
        System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.CreateSpecificCulture(_userLanguage);
      else
        System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.CreateSpecificCulture(„en-US“);
    }
    else
      System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.CreateSpecificCulture(„en-US“);
  }
}
catch
{ // jinak nastav en-US
  System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(„en-US“);
}
// přepni i UI Culture na aktuálně zvolené culture
System.Threading.Thread.CurrentThread.CurrentUICulture = System.Threading.Thread.CurrentThread.CurrentCulture;

Pro skutečné nasazení by bylo potřeba zajistit nějaký výčet povolených jazyků, jinak si můžeme přivodit aplikaci vypadající jako v Kocourkově: kalendář bude zobrazen v ruštině azbukou, datum bude v ruském formátu, části našich textů budou anglicky – tedy je potřeba nějak zajistit, aby se aplikace nepřepínala do libovolného Culture dle jazyka, s jakým klient přišel, ale jen do takového, který podporujeme, v ukázce to pro jednoduchost neřeším. Také tuto část kódu by se patřilo umístit do souboru Global.asax, kde se hodí ji provádět ihned v obsluze události Begin_Request().

Další otázkou je, jak udržet jednotlivé jazykové verze navzájem konzistentní – triviálním řešením je spravovat jednotlivé verze jako sloupce v sešitu tabulkového procesoru (např. MS Excel), ve firemním prostředí se hodí i list na SharePointu. Pro pohodlnou správu je důležité nejen udržet konzistenci mezi verzemi různých jazyků (aby žádný klíč nechyběl nebo nějaký zastaralý nepoužívaný nepřebýval), ale také aby nebyly v názvech klíčů v jednotlivých zdrojových souborech překlepy – tedy aby se někde zvlášť spravovaný seznam textů dal pohodlně vyexportovat do zdrojových souborů. V již zmíněném Excelu lze například napsat makro, které takový export zajistí.

Pokud se vám podaří zvládnout zde popsané problémy a nové aplikace budete psát již s úmyslem je lokalizovat, zjistíte, že dát k dispozici aplikaci přizpůsobující se jazyku uživatele je velmi snadné – a jazykové assembly budete doslova „sekat jako Baťa cvičky“. Veškeré popsané soubory a soubory ukázky si můžete stáhnout.

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Žádný příspěvek v diskuzi

Odpovědět