Jaromír Nechanický

Vývojářský blog Jaromíra Nechanického

Podle kategorie

Tvorba lokalizovatelné aplikace

Jaromír Nechanický       10.10.2009             11744 zobrazení

Zajisté jste již také narazili na problém, kdy potřebujete Vaši aplikaci přeložit do cizího jazyka. “Ideálním” a bohužel velmi častým případem je, když si na to šéf vzpomene až po dokončení celé aplikace, jejíž návrh s touto možností samozřejmě nepočítal. A i kdyby to bylo v původních požadavcích, používání resources pro texty GUI při vývoji je velmi nepohodlné. Naštěstí má Visual Studio prostředky, jak nám náležitě ulehčit život.

Mějme klasickou již hotovou aplikaci. U formuláře, který chcete nastavit jako lokalizovatelný dejte jeho vlastnosti a najdete položku “Localizable”. Tu nastavte na True.

image

Nyní si všimněte, že VS vygenerovalo soubor $název formu$.resx.

image

V tomto souboru jsou extrahovány veškeré textové řetězce z GUI formuláře. Podíváte-li se na obsah tohoto souboru, uvidíte, že navíc obsahuje i informace o nastavení všech komponent (zobrazíte kliknutím na “Strings” a vyberte Other). Nyní přidáme resource soubor pro češtinu. Klikněte na “add new item” a zde v kategorii general vybereme resource file. Resource soubor musíte pojmenovat $název formu$.$culture$.resx. Například pro můj Form1 a češtinu bude jméno Form1.cs.resx. Do tohoto souboru nakopírujte texty z původního resource souboru.

Tip: pokud si nepamatujete/neznáte názvy jednotlivých kultur, spusťte v PowerShellu příkaz “[Globalization.CultureInfo]::GetCultures([Globalization.CultureTypes]::NeutralCultures)”

Nyní je nejvyšší čas odeslat soubor překladatelce. Formát resx je sice jednoduché XML, ze kterého by každý programátor/administrátor byl nadšen.

 <?xml version="1.0" encoding="utf-8"?>
<
root>
<!--

Microsoft ResX Schema

Version 2.0

The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.

Example:

... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>

There are any number of "resheader" rows that contain simple
name/value pairs.

Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.

The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:

Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.

mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.

mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.

mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<
xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<
xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<
xsd:element name="root" msdata:IsDataSet="true">
<
xsd:complexType>
<
xsd:choice maxOccurs="unbounded">
<
xsd:element name="metadata">
<
xsd:complexType>
<
xsd:sequence>
<
xsd:element name="value" type="xsd:string" minOccurs="0" />
</
xsd:sequence>
<
xsd:attribute name="name" use="required" type="xsd:string" />
<
xsd:attribute name="type" type="xsd:string" />
<
xsd:attribute name="mimetype" type="xsd:string" />
<
xsd:attribute ref="xml:space" />
</
xsd:complexType>
</
xsd:element>
<
xsd:element name="assembly">
<
xsd:complexType>
<
xsd:attribute name="alias" type="xsd:string" />
<
xsd:attribute name="name" type="xsd:string" />
</
xsd:complexType>
</
xsd:element>
<
xsd:element name="data">
<
xsd:complexType>
<
xsd:sequence>
<
xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<
xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</
xsd:sequence>
<
xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<
xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<
xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<
xsd:attribute ref="xml:space" />
</
xsd:complexType>
</
xsd:element>
<
xsd:element name="resheader">
<
xsd:complexType>
<
xsd:sequence>
<
xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</
xsd:sequence>
<
xsd:attribute name="name" type="xsd:string" use="required" />
</
xsd:complexType>
</
xsd:element>
</
xsd:choice>
</
xsd:complexType>
</
xsd:element>
</
xsd:schema>
<
resheader name="resmimetype">
<
value>text/microsoft-resx</value>
</
resheader>
<
resheader name="version">
<
value>2.0</value>
</
resheader>
<
resheader name="reader">
<
value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</
resheader>
<
resheader name="writer">
<
value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</
resheader>
<
data name="$this.Text" xml:space="preserve">
<
value>Form1</value>
</
data>
<
data name="&gt;&gt;$this.Name" xml:space="preserve">
<
value>Form1</value>
</
data>
<
data name="&gt;&gt;$this.Type" xml:space="preserve">
<
value>System.Windows.Forms.Form, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</
data>
<
data name="&gt;&gt;button1.Name" xml:space="preserve">
<
value>button1</value>
</
data>
<
data name="&gt;&gt;button1.Parent" xml:space="preserve">
<
value>$this</value>
</
data>
<
data name="&gt;&gt;button1.Type" xml:space="preserve">
<
value>System.Windows.Forms.Button, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</
data>
<
data name="&gt;&gt;button1.ZOrder" xml:space="preserve">
<
value>0</value>
</
data>
<
data name="button1.Text" xml:space="preserve">
<
value>Ahoj svete</value>
</
data>
</
root>

Bohužel většina lidí mimo obor naše nadšení nesdílí. Zde přichází ke slovu utilita ResGen.exe (distribuovaná s VS, případně Windows SDK), která umí toto XML konvertovat na textový soubor a naopak.

Spustíme příkaz “resgen Form1.cs.resx Form1.cs.txt”. Vygenerovaný textový soubor je podstatně přijatelnější pro další (neprogramové) zpracování. Změníme v něm potřebné texty například na anglické a opět spustíme “resgen Form1.cs.txt Form1.en.resx”.

Nyní ve Visual Studiu zvolíme “add existing item” a najdeme náš nový soubor s resources. Spustíte-li aplikaci, jakoby se nic nestalo. Ovšem přidáte-li do konstruktoru před “InitializeComponent()” tento řádek kódu:

Thread.CurrentThread.CurrentUICulture = System.Globalization.CultureInfo.GetCultureInfo("en-US");

Nyní se aplikoval soubor s angličtinou. .NET se tedy automaticky podívá na nastavení aplikace (jaká je nastavená kultura při spuštění) a podle toho zvolí nejvhodnější soubor s texty. Pokud není specifický soubor pro danou zemi, použije se defaultní (Form1.resx).

Podíváme-li se k výslednému exe souboru aplikace, uvidíme, že zde přibyly nové složky. Tyto složky je třeba distribuovat spolu s aplikací, protože v nich jsou uloženy lokalizované texty.

 

hodnocení článku

-1 bodů / 3 hlasů       Hodnotit mohou jen registrované uživatelé.

 

Nový příspěvek

 

Diskuse: Tvorba lokalizovatelné aplikace

Ještě jsem neviděl nikoho, kdo by lokalizaci formulářů prováděl takto nehorázně. Příslušné resource soubory se automaticky vytvoří při výběru jazyka formuláře (vlastnost Language) a nic není třeba přidávat ručně. Kromě toho není v článku ani zmínka o neutrální kultuře, což je škoda, protože to je jedna ze základních věcí která ovlivňuje chování lokalizované aplikace.

nahlásit spamnahlásit spam -34 / 34 odpovědětodpovědět

Je to asi věc názoru. Mě přijde naprosto nevhodné Vaše řešení a to z jednoho prostého důvodu: nedá se rozumě automatizovat. U každého formuláře bych totiž musel naklikat všechny možné jazyky, kdežto u ručního přidávání stačí toto provést pouze pro základní jazyk a o zbytek se postará najatá překladatelka v kombinaci s několika řádky v PowerShellu. Vaše řešení mi připadá lepší pro malé aplikace, ovšem u čehokoliv většího bych si odrovnal zápěstí :-)

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Člověče co to plácáte za nesmysly?! Jaká automatizace? Kliknete pouze tolikrát, do kolika jazyků chcete lokalizovat, kdežto ve vašem případě budete kromě ručního přidávání příslušných ResX souborů také pokaždé kopírovat jejich obsah. O tom co dostane překladatelka to je už jiná kapitola. A jestli pro takto triviální úlohy používáte PowerShell tak to je tedy k pousmání. Moje řešení je běžně používané a doporučované pro jakoukoliv lokalizaci.

nahlásit spamnahlásit spam 0 / 2 odpovědětodpovědět

Až budete lokalizovat aplikaci se 150 okny do šesti jazyků, tak už to smysl má.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Ano kliknete pouze tolikrát, kolik potřebujete jazyků a pro každý formulář. Zároveň nevím, jak Vám, ale mne VS nevygeneruje nic, dokud se nějaký text nezmění, což znamená, že musím zároveň přepsat kus textu (toto chování může být způsobeno nastavením VS, nepátral jsem). Počty jsou nyní již jednoduché:

Aplikace má 15 formulářů, chci to přeložit do 3 jazyků, včetně základního. To Vám dělá 15*3 a k tomu ještě změna alespoň jediného textu (pokud potom obsah budete kopírovat stejně jako já, případně vyplňovat ručně, protože do těchto souborů se promítne pouze změněný text).

V mém řešení stačí pouze všem formulářům nastavit Localizable (15 kliknutí) a poté rozkopírovat příslušný textový soubor a jen ho přejmenovat (jeden PS command). Překladatelka přeloží a opět jedním PS commandem uděláte ze souborů resx, které třemi kliky přidáte do projektu (všechny). Myslím, že co do složitosti vedu ;-)

Nicméně rád se přiučím. Můžete mi prosím popsat, jak to u Vás funguje? Vytvořím tedy resx soubory pro všechny formy a všechny jazyky. V jakém formátu pak texty dáváte překladatelce a jak je poté dáváte do aplikace?

nahlásit spamnahlásit spam 2 / 2 odpovědětodpovědět

Překladatelka není potřeba (není nic horšího než nechat si externě překládat odborné věci), texty se lokalizují přímo ve Visual Studiu v příslušném ResX souboru.

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Cože, překladatelka není potřeba? To je opravdu úlet. Vy jste možná dokonalý a umíte perfenktně všechny jazyky světa, ale běžné to rozhodně není ;-)

Článek se mi moc líbí!

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Všechny jazyky světa rozhodně nejsou potřeba. Běžně se překládá pouze do angličtiny, maximálně do němčiny. Pokud někdo není úplný dement a nemá problémy se čtením odborné anglické literatury, MSDN a podobně, tak uživatelské rozhraní je schopen si bez problémů přeložit sám. Překládáte snad román nebo co?! Překladatelce bych svěřil možná tak nápovědu a dokumentaci.

nahlásit spamnahlásit spam -32 / 32 odpovědětodpovědět

Aha, tak pokud pro Vás existují jen čeština, angličtina a němčina, tak se opravdu nemá cenu s Vámi dohadovat...

nahlásit spamnahlásit spam 0 / 2 odpovědětodpovědět

Aha, a pro kolik jazyků se lokalizují vaše aplikace? Nemyslím nějaké plácání webových stránek, ale klasické desktopové aplikace... Probuďte se prosím.

nahlásit spamnahlásit spam -32 / 32 odpovědětodpovědět

Co se mne a desktopových aplikací týče, tak například za poslední půl rok jsem krom AGJ, NJ a CZ (mimochodem i na NJ již potřebuji někoho, kdo to přeloží) lokalizoval do slovenštiny, polštiny a maďarštiny.

Opět se jedná o typ aplikace, kterou píšete. Je samozřejmé, že krabicovou distribuci malá firmička nebude lokalizovat jinam, než do AGJ. Ovšem pokud píšete aplikaci na zakázku, se kterou budou pracovat i lidé mající pouze ZŠ vzdělání, musí to být v jejich mateřštině. Navíc pokud Vám zákazník zaplatí několik set tisíc za aplikaci na zakázku, nemůžete mu říct, že v civilizovaných zemích se mluví anglicky a ať se to kouká naučit

nahlásit spamnahlásit spam 2 / 2 odpovědětodpovědět

Naše desktopová aplikace je lokalizovaná do 33 jazyků. Vy se probuďte, svět není jen o malých desktopových aplikacích na zakázku...

nahlásit spamnahlásit spam 3 / 3 odpovědětodpovědět

Malé desktopové aplikace na zakázku sem furt cpete vy. Pracoval jsem na velmi rozsáhlém projektu (řádově rozsáhlejší aplikace než ubohý avast), který nebylo ani potřeba lokalizovat, takže velikost projektu rozhodně neznamená nutnou potřebu lokalizace.

nahlásit spamnahlásit spam -29 / 29 odpovědětodpovědět

Nejdříve jste napadal samotné řešení. Když bylo obhájeno, tak jste začal napadat důvod jeho použití. To jste argumentoval prakticky nulovou potřebou lokalizovat, což bylo nesmyslně obhájeno tím, že na projektu, který jste dělal lokalizace potřeba není (resp nikdo ji nevyžadoval / nechtěl zaplatit). A nakonec jste skončil u napadnutí Avastu.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Aneb kdo chce psa bít, hůl si vždy najde :)

nahlásit spamnahlásit spam 3 / 3 odpovědětodpovědět

Článek je sice pěkný, ale bohužel velmi často není potřeba překládat jen Formy, ale i ostatní věci, ketré na formuláři nejsou, přesto je třeba je mít v daném jazyku (např. MsgBox, ToolBar, atd.) Tam tento způsob použitelný není, docela jsem se s tím trápil, než jsem přišel na to, jak to ve VB.NET udělat. Možná by se to někomu hodilo, kdyby to tu pro ostatní bylo autorem taky ukázáno.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Dobrý den,

a jak jste to řešil?

Díky.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Proboha co je to za nesmysly?!

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět
                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:
Heslo:

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback