Internacionalizace a lokalizace v C++

Ódy žlutého koně 4.

Ódy žlutého koně počtvrté a naposledy: Formátování čísel

V předchozích dílech článku věnovaného problematice lokalizace, resp. internacionalizace programů v C++ jsme se seznámili s třídou std::locale a s fazetami, naučili jsme se číst a zapisovat český text v různých kódováních, převádět malá písmena na velká a naopak a řadit znakové řetězce podle abecedy. V posledním dílu se podíváme na formátování čísel.

Národní zvyklosti

Každý začínající programátor se učí, že v reálných číslech musí v programech psát desetinnou tečku, nikoli čárku, a že nesmí používat mezery pro oddělování tisíců. Jenže to je vlastně nepřirozené: celá kontinentální Evropa používá desetinnou čárku a nějaké oddělovače tisíců. Podíváme-li se za hranice Evropy, zjistíme, že jiné národy seskupují číslice ve víceciferných číslech i ji.nak než po třech (takže vlastně ani nelze hovořit o oddělovačích tisíců).

Pro nás je přirozené napsat číslo milión 1 000 000,00, zatímco Němec napíše 1.000.000,00 a Angličan pro změnu 1,000,000.00. V Nepálu prý totéž číslo zapíší jako 10.00.000,00.
Navíc se tuto zvyklosti mohou v průběhu času vyvíjet, takže je jasné, že i tyto odlišnosti nelze dost dobře naprogramovat jednou pro vždy, ale že je třeba je načerpat stejně jako jiné součásti národního nastavení např. z dat uložených v operačního systému – tak, jako to dělá zpravidla třída locale.

Fazety pro formátování čísel

O formátování čísel se starají fazety numpunct, num_get a num_put.

- Fazeta numpunct definuje znak pro desetinnou čárku (nebo tečku), oddělovače tisíců a jména pro logické hodnoty.
- Fazeta num_get se stará o analýzu vstupního řetězce, který obsahuje číslo.
- Fazeta num_put má na starosti formátování vystupujícího řetězce, který představuje číselnou hodnotu.

Fazety num_get a num_put používají fazetu numpunct.

Implicitní nastavení
Podívejme se, jak dopadne výstup celého a reálného čísla v českém národním nastavení tak, jak je k dispozici ve Visual C++ .NET pod Vindows 2000. Vezměme následující jednoduchý program:

// Test českého národního nastavení
// pro celá a reálná čísla
#include <locale>
#include <iostream>
using namespace std;
int main()
{
          locale L("Czech_Czech Republic.852");
          double d = 123456.7;
          int i = 100000;
          wcout << fixed << boolalpha;
          wcout << L"implicitni nastaveni: " << d << endl
                        << i << endl << true << endl;
          wcout.imbue(L);
          wcout << L"české nastavení: " << d << endl << i << endl
                       << true << endl;
          return 0;
}

Zde nejprve vypíšeme reálné a celé číslo s implicitním nastavením a pak s českým nastavením. Výstup tohoto programu bude

implicitni nastaveni: 123456.700000
1000000
true
české nastavení: 123456,700000
1000000
true

Je tedy zřejmé, že vše, čeho jsme dosáhli, je, že se ve výstupu reálného čísla objevila desetinná čárka. (Tu nyní musíme použít i při vstupu.) Implicitní nastavení neobsahuje oddělovače tisíců a česká jména pro logické hodnoty. Pokud opravdu trváme na oddělovačích tisíců, musíme si je naprogramovat, a podobně pokud chceme používat jiná jména pro hodnoty logických konstant true a false.

Fazeta numpunct
Cesta ke změně formátování při vstupních a výstupních operacích v datových proudech vede přes fazetu std::numpunct<>. Ve standardní knihovně jazyka C++ je definována takto:

namespace std {
          template <class charT>
          class numpunct : public locale::facet {
          public:
                    typedef charT char_type;
                    typedef basic_string<charT> string_type;
                    explicit numpunct(size_t refs = 0);
                    char_type decimal_point() const;
                    char_type thousands_sep() const;
                    string grouping() const;
                    string_type truename() const;
                    string_type falsename() const;
                    static locale::id id;
          protected:
                    ~numpunct(); //virtuální
                    virtual char_type do_decimal_point() const;
                    virtual char_type do_thousands_sep() const;
                    virtual string do_grouping() const;
                    virtual string_type do_truename() const; // pro typ bool
                    virtual string_type do_falsename() const; // pro typ bool
          };
}

Šablonový parametr charT znamená jako obvykle znakový typ, tedy char nebo wchar_t. Metody decimal_point(), thousands_sep(), truename() a falsename() vracejí po řadě znak pro desetinnou čárku, oddělovač tisíců, znakový řetězec pro hodnotu true a znakový řetězec pro hodnotu false. První dvě metody vracejí hodnotu typu charT, poslední dvě vracejí metodu typu basic_string<charT>. (To znamená, že typ vracené hodnoty se tedy liší podle toho, zda pracujeme s úzkými nebo se širokými znaky.)

Metoda grouping() vrací znakový řetězec typu string (vždy úzké znaky, tedy typ basic_string<char>) který určuje rozdělení číslic v celých číslech do skupin. Vrátíme se k ní v následujícím odstavci.

Tyto metody nedělají nic jiného, než že volají odpovídající virtuální metody, pojmenované do_decimal_point(), do_thousands_sep(), do_grouping(), do_truename() a do_falsename(). To umožňuje použít na místě fazety numpunct – tedy na místě instance třídy numpunct – instanci jakékoli odvozené třídy.

Statická složka id typu std::locale::id slouží k identifikaci fazety; používá se interně jako klíč při vyhledávání fazety v instanci třídy locale. V potomkovi třídy std::numpunct ji není třeba definovat. (Přesněji, nesmíme ji definovat znovu, jinak by ji třída locale chápala jako jiný typ fazety.)

Skupiny číslic
Už víme, že řetězec vrácený metodou grouping() určuje způsob seskupování číslic. Jestliže nechceme číslice nijak seskupovat, zadáme prázdný řetězec "" nebo "\0". Pro nejběžnější způsob, seskupování po třech, zadáme "\3". Pokud bychom si přáli nejprve skupinu 4 číslic, pak skupinu dvou a nakonec skupinu 3 číslic, zadali bychom řetězec "\4\2\3".

Přesné pravidlo je jednoduché: zadaný znakový řetězec se bere jako pole čísel. Číselná hodnota prvního prvku udává velikost první skupiny (skupiny nejnižších číslic), číselná hodnota druhého prvku udává velikost druhé skupiny atd. Je-li číslo příliš velké a odpovídající skupina není řetězcem vráceným funkcí grouping() popsána, použije se pro ni velikost poslední skupiny. To znamená, že poslední velikost skupiny se stále opakuje.

Příklad
Napíšeme si vlastní fazetu pro formátování čísel. Následující program ukazuje, že formátování se uplatní nejen při výstupu, ale i při čtení:

// Definice a použití vlastní fazety
// pro formátování čísel a logických hodnot
#include <iostream>
#include <stdexcept>
#include <fstream>
#include <locale>
using namespace std;
class punkt_w: public std::numpunct<wchar_t> {
public:
          typedef wchar_t char_type;
          typedef wstring string_type;
          explicit punkt_w(size_t r = 0): std::numpunct<wchar_t>(r){}
protected:
          char_type do_decimal_point() const {return ',';}
          char_type do_thousands_sep() const {return ' ';}
          string do_grouping() const {return "\003";}
          string_type do_truename() const {return L"pravda";};
          string_type do_falsename() const {return L"nepravda";};
};
wchar_t cw[] = L"Žluťoučký kůň příšerně úpěl ďábelské ódy.";
int main()
{
          try{
                    locale Cr("Czech_Czech Republic.852");
                    locale Kon(Cr, new punkt_w(0));
                    wcout.imbue(Kon);
                    wcin.imbue(Kon);
                    wcout << cw << endl;
                    bool b = true;
                    int n = 12345;
                    double d = 12.345678;
                    wcin >> boolalpha;
                    wcout << boolalpha << b << endl;
                    wcout << n << endl;
                    wcout << d << endl;
                    wcout << L"zadej reálné číslo: ";
                    wcin >> d;
                    wcout << L"dvojnásobek je "<< d*2 << endl;
                    wcout << L"zadej celé číslo: ";
                    wcin >> n;
                    wcout << L"dvojnásobek je "<< 2*n << endl;
                    wcout << L"zadej logickou hodnotu: ";
                    wcin >> b;
                    wcout << L"Opak je " << !b << endl;
          }
          catch(runtime_error &e)
          {
                    cerr << e.what() << endl;
          }
          return 0;
}

Fazetu punct jsme odvodili jako potomka fazety std::numpunct<wchar_t> (pouze pro široké znaky). V ní jsme definovali mezeru jako oddělovač tisíců, čárku jako desetinnou tečku a řetězce pravda, resp. nepravda jako slovní reprezentaci hodnot true, resp. false.

Na počátku funkce main() vytvoříme instanci Cr třídy locale reprezentující české národní nastavení; pak vytvoříme instanci Kon, která převezme z instance Cr všechny fazety kromě numpunct; tu nahradí instancí fazety punct.

Abychom se přesvědčili, že instance Kon opravdu převzala ostatní fazety z instance Cr, vypíšeme nejprve oblíbenou větu o žluťoučkém koni.

Pak pomocí manipulátoru boolalpha předepíšeme, že požadujeme vstup a výstup logických hodnot ve formě znakového řetězce, nikoli v podobě čísel 0 nebo 1.

Konverzace s tímto programem může vypadat takto:

Žluťoučký kůň příšerně úpěl ďábelské ódy.
pravda
12 345
12,3457
zadej reálné číslo: 3,1415926
dvojnásobek je 6,28319
zadej celé číslo: 1 000 000 000
dvojnásobek je 2 000 000 000
zadej logickou hodnotu: nepravda
Opak je pravda

Doplňme, že při zadávání celých čísel můžeme mezery, představující oddělovače tisíců, vynechat. (Musíme ale buď všechny vynechat, nebo všechny uvést, jinak se číslo správně nepřečte. Nesmíme také nikde použít více než jednu mezeru.)

Změníme-li řetězec, vracený metodou do_grouping(), na "\4\2\3", vypíše se číslo 2000000000 ve tvaru

2 000 00 0000

Všimněte si, že seskupování a oddělovače tisíců se nepoužily pro reálná čísla. To je obecné pravidlo: fazeta numpunct určuje pouze desetinnou čárku (tečku), nikoli seskupování a oddělovače číslic. Pokud bychom něco takového chtěli, musíme si to naprogramovat sami.

Poznamenejme, že kdybychom jako řetězec, vracený funkcí do_grouping(), místo "\3"použili "3", seskupování číslic v celých číslech by se neuplatnilo. Znak '3' totiž představuje číselnou hodnotu 51, takže by se program pokusil vytvořit skupiny po 51 číslicích.

Poznámka
Formátování celých čísel, které jsme zde definovali, se uplatní pouze pro proudy používající široké znaky. Budeme-li chtít podobným způsobem formátovat vstup a výstup čísel a logických hodnot pro úzké znaky, budeme si muset napsat podobnou fazetu i pro typ char.

Fazetu punct se tedy vyplatí deklarovat jako šablonu s parametrem, který bude určovat použitý znakový typ. Přitom ovšem narazíme na problém se znakovými literály; současné C++ totiž nenabízí elegantní způsob, jak definovat řetězcový literál, který by byl v závislosti na parametru šablony typu char nebo wchar_t. Jedno z možných řešení je použít statická znaková pole a inicializovat je prvek po prvku, neboť pro typ char je definována implicitní konverze na wchar_t:

template<typename charT>
class punkt: public std::numpunct<charT> {
public:
          typedef charT char_type;
          typedef basic_string<charT> string_type;
          explicit punkt(size_t r = 0): std::numpunct<charT>(r){}
protected:
          char_type do_decimal_point() const {return ',';}
          char_type do_thousands_sep() const {return ' ';}
          string do_grouping() const {return "\003";}
          string_type do_truename() const {return string_type(true_name);};
          string_type do_falsename() const {return string_type(false_name);};
private:
          static char_type true_name[];
          static char_type false_name[];
};
template<class charT>
punkt<charT>::char_type punkt<charT>::true_name[] =
                              {'p','r','a','v','d','a','\0'};
template<class charT>
punkt<charT>::char_type punkt<charT>::false_name[] =
                              {'n','e','p','r','a','v','d','a','\0'};

Začátek funkce main() pak upravíme následujícím způsobem:

int main()
{
          try{
                    locale Cr("Czech_Czech Republic.852");
                    locale Kon0(Cr, new punkt<wchar_t>(0));
                    locale Kon(Kon0, new punkt<char>(0));
                    wcout.imbue(Kon);
                    wcin.imbue(Kon);
                    cout.imbue(Kon);
                    // ... a dále jako předtím
}

Fazety přidáváme do instance třídy locale jednu po druhé.

Vlastní fazeta

Nyní si ukážeme, jak lze definovat a používat vlastní fazetu. Jde o upravený příklad z dodatku D knihy [1].

Definujeme fazetu, která bude sloužit ke vstupu a výstupu názvů ročních období a kterou půjde použít jen pro úzké znaky. Pro široké znaku je postup podobný.
Nejprve definujeme výčtový typ popisující roční období:

enum obdobi {jaro, leto, podzim, zima};

Je-li s proměnná typu obdobi obsahující hodnotu podzim, měl by náš program po zadání příkazu

cout << s;

vypsat řetězec podzim. Podobně při vstupu dat tohoto typu chceme zadávat řetězce přerdstavující jména ročních období, nikoli čísla.

Fazeta Obd_vv
Na první pohled se může zdát, že k tomu stačí přetížit v hodným způsobem operátory << a >>, není třeba zatěžovat se s fazetami národního nastavení.

To je pravda, pokud nám jde o jeden program. Pokud bychom ovšem chtěli upravit používání typu obdobi v širším kontextu – např. ve všech překladačích, a pokud bychom chtěli umožnit používání řetězců k práci s ročními obdobími i v jiných národních prostředích, má smysl definovat pro tento účel fazetu.

Rozhodneme se tedy, že definujeme fazetu Obd_vv (vstup a výstup ročního období). Abychom umožnili dalším programátorům vytvořit odpovídající fazety pro jiná národní nastavení, bude rozumné definovat Obd_vv jako abstraktní třídu a od ní odvodit fazetu pro české národní nastavení:

class Obd_vv: public locale::facet {/* ... */ }; // abstraktní fazeta
class CZ_Obd_vv: public Obd_vv{/* ... */ }; // česká verze

Přetížené operátory << a >> budou se budou odvolávat na abstraktní třídu Obd_vv. To znamená, že veškeré služby, které tato třída bude poskytovat, musí být implementovány jako virtuální metody.

Fazeta pro roční období musí umět konvertovat hodnotu výčtového typu na znakový řetězec a znakový řetězec na hodnotu výčtového typu. Přesněji, musí umět konvertovat alespoň hodnoty odpovídající některé z výčtových konstant. Proto bude obsahovat metody, které vtipně pojmenujeme to_str() a from_str():

class Obd_vv: public locale::facet {
public:
          Obd_vv(int i = 0): locale::facet(i){}
          ~Obd_vv(){}
          virtual const string& to_str(obdobi x) const = 0
          virtual bool from_str(const string& s, obdobi& x) const = 0;
          static locale::id id;
};
locale::id Obd_vv::id;

Metoda from_str() vrací logickou hodnotu indikující, zda se zadanou hodnotu podařilo konvertovat, tj. zda x obsahuje hodnotu odpovídající některé z konstant.
Připomeňme si, že tato třída musí obsahovat statickou složku id typu std::locale::id, kterou bude třída locale používat k její identifikaci.

Od třídy Obd_vv odvodíme implementaci pro české nastavení, kterou nazveme např. CZ_Obd_vv:

class CZ_Obd_vv: public Obd_vv
{
          static const string nazvy[];
public:
          const string& to_str(obdobi) const;
          bool from_str(const string&, obdobi&) const;
};
const string CZ_Obd_vv::nazvy[] = {"jaro","leto","podzim","zima"};

v níž budou již definovány metody to_str() a from_str(). Dále ovšem musíme přetížit operátory << a >>, aby při výstupu využívaly služeb této fazety. Podívejme se nyní na celý program:

// Definice a použití vlastní fazety
// pro formátování čísel a logických hodnot
#include <iostream>
#include <stdexcept>
#include <fstream>
#include <locale>
#include <algorithm>
using namespace std;
enum obdobi {jaro, leto, podzim, zima};
class Obd_vv: public locale::facet { // Abstraktní předek
public:
          Obd_vv(int i = 0): locale::facet(i){}
          ~Obd_vv(){}
          virtual const string& to_str(obdobi x) const = 0;//{static const string s("");return s;};
          virtual bool from_str(const string& s, obdobi& x) const = 0; //{return true;}
          static locale::id id;
};
locale::id Obd_vv::id;
class CZ_Obd_vv: public Obd_vv // Implementace pro české
{ // nastavení

          static const string nazvy[];
public:
          const string& to_str(obdobi) const;
          bool from_str(const string&, obdobi&) const;
          //CZ_Obd_vv(int i = 0): Obd_vv(i){}
};
const string CZ_Obd_vv::nazvy[] = {"jaro","léto","podzim","zima"};
// Implementace metod pro převod na řetězec a z něj
const string& CZ_Obd_vv::to_str(obdobi x) const
{
          static const string s("nesprávné období");
          if(x < jaro || x > zima) return s;
                    else return nazvy[x];
}
bool CZ_Obd_vv::from_str(const string& s, obdobi& x) const
{
          const string* p = find<const string*, string>(nazvy, &nazvy[zima+1], s);
          if(p == &nazvy[zima+1]) return false;
          x = obdobi(p - nazvy);
          return true;
}
// Přetížené operátory pro vstup a výstup,
// které využívají služeb fazety Obd_vv

ostream& operator<<(ostream& Proud, obdobi x)
{
          const locale& loc = Proud.getloc();
          if(has_facet<Obd_vv>(loc))
                    return Proud << use_facet<Obd_vv>(loc).to_str(x);
          else
                    return Proud << int(x);
};
istream& operator>>(istream& Proud, obdobi &x)
{
          const locale &loc = Proud.getloc();
          if(has_facet<Obd_vv>(loc)){
                    const Obd_vv& f = use_facet<Obd_vv>(loc);
                    string buf;
                    if(!(Proud >> buf && f.from_str(buf, x)))
                              Proud.setstate(ios_base::failbit);
                    return Proud;
                    }
                    int i;
                    Proud >> i;
                    x = obdobi(i);
                    return Proud;
}
int main()
{
          try{
                    locale Cr("Czech_Czech Republic.852");
                    locale Kon(Cr, new CZ_Obd_vv);
                    obdobi ob = leto;
                    cout.imbue(Kon);
                    cin.imbue(Kon);
                    cout << ob << endl;
                    cin >> ob;
                    cout << int(ob) << endl;
          }
          catch(runtime_error &e)
          {
                    cerr << e.what() << endl;
          }
          return 0;
}

Implementace metody to_str() pro převod hodnoty typu obdobi na znakový řetězec je přímočará a nevyžaduje komentáře. Implementace metody from_str() je jen nepatrně složitější. K vyhledání názvu s v poli názvů ročních období používá funkce find<>(), jejíž šablona je v hlavičkovém souboru <algorithm>.

const string* p = find<const string*, string>(nazvy, &nazvy[zima+1], s);

První parametr udává ukazatel na první prvek prohledávaného úseku, druhý udává ukazatel za poslední prvek a třetí je hledaná hodnota. Tato funkce vrátí ukazatel na nalezený řetězec nebo ukazatel na poslední prvek, pokud daný řetězec nenajde. Příkaz

x = obdobi(p - nazvy);

zjistí pomocí adresové aritmetiky index nalezeného prvku, přetypuje ho na obdobi a uloží ho do proměnné x, v níž se vrací výsledek.

Implementace operátoru << pro výstup je opět poměrně přímočará. Tato funkce nejprve získá odkaz na instanci třídy locale datového proudu,

const locale& loc = Proud.getloc();

Pak si zjistí, zda tato instance obsahuje fazetu Obd_vv. Pokud ano, použije její metodu to_str() k převodu na hodnoty typu obdobi řetězec, jinak vypíše zadanou hodnotu jako číslo. Přetypování na int v příkazu

return Proud << int(x);

je nezbytné, neboť jinak by se rekurzivně volal operátor << pro typ obdobi.

Implementace operátoru >> pro vstup je poněkud složitější. Také tato funkce nejprve získá odkaz na instanci třídy locale datového proudu a zjistí, zda obsahuje potřebnou fazetu. Pokud ne, přečte prostě celé číslo, přetypuje ho na obdobi a výsledek uloží do parametru x. Pokud ovšem instance třídy locale potřebnou fazetu obsahuje, přečte z proudu znakový řetězec a pokusí se ho převést na typ obdobi pomocí metody from_str() naší fazety; výsledek uloží do parametru x. Jestliže při čtení nebo při převodu řetězce neuspěje, nastaví příznak chyby v proudu.

Poznámka
Budete/li si tento příklad zkoušet, můžete narazit na následující problém: Některé starší implementace standardní knihovny jazyka C++ (např. Borland C++Builder 4 a 5 nebo Microsoft Visual C++ 6) si z nějakých záhadných důvodů vytvářejí také instanci předka použité fazety. V našem případě se tedy pokusí vytvořit instanci třídy Obd_vv; pochopitelně ohlásí chybu, že nemohou vytvořit instanci abstraktní třídy. Pomoc je snadná, stačí abstraktním dát metodám implementaci – stačí jakákoli, neboť nebudou použity.

class Obd_vv: public locale::facet { // Když nesmí být abstraktní
public:
          Obd_vv(int i = 0): locale::facet(i){}
          ~Obd_vv(){}
                    virtual const string& to_str(obdobi x) const {
                    static const string s("");
          return s;
          }
          virtual bool from_str(const string& s, obdobi& x){ return true; }

          static locale::id id;
};

Na závěr je třeba ještě jednou zdůraznit, že definice vlastní fazety je něco, co do běžného programu nepatří; samotné přetížení operátorů >> a << obstará totéž jednodušeji.

O čem jsme nehovořili

I když naše povídání o národním prostředí mělo čtyři díly, nepokrývá vše, co by o této problematice stálo za napsání. Nehovořili jsme například o formátování datových a časových údajů, o formátování měnových údajů nebo o práci s katalogem zpráv. I když jde o věci na první pohled potřebné, práce s nimi není tak snadná jako práce s řetězci nebo s čísly. Jazyk C++ totiž neobsahuje standardní třídu Date (nebo jinou, která by se hodila k práci s časovými údaji), stejně jako neobsahuje např. třídu pro reprezentaci peněžních částek. Pokud něco takového chceme, musíme si to naprogramovat sami. Nehovořili jsme také o práci s národním prostředím v jazyce C.

Upozornění

Podobně jako v předchozích dílech, i zde musím čtenáře upozornit, že současné překladače s národním nastavením nezacházejí příliš korektně. Nebuďte tedy překvapeni, když uvedené příkladu nebudou ve vašem překladači fungovat.

V mnoha starších překladačích narazíme na problémy, jež pocházejí ze skutečnosti, že tyto překladače neimplementují vnořené šablony, neumějí použít jeden parametr šablony jako implicitní hodnotu následujícího parametru, nepodporují explicitní specifikaci parametry šablony u šablonové funkce atd. Místo funkcí use_face<>() a has_face<>() pak musíme používat různá makra.

Většina současných běžně rozšířených překladačů si také neporadí s překladem úzkých znaků z jednoho kódování do jiného; proto jsem v některých příkladech používal pouze široké znaky. (Ostatně některé si neporadí ani s překladem širokých znaků, pokud obsahují znaky národní abecedy – a je jedno, zda jde o abecedu českou, polskou, německou nebo francouzskou).
Některé překladače také nekorektně zacházejí s předky fazetových tříd (o tom jsme mluvili v tomto pokračování v oddílu věnovaném vlastní fazetě).

Všechny příklady zde uvedené ve všech dílech tohoto článku jsem přeložil a vyzkoušel ve Visual C++ .NET. Ani tento překladač se ovšem nechová zcela korektně (nezvládá překódování úzkých znaků, jestliže obsahují znaky národních abeced).

Kde získat další informace

Problematice národního nastavení se věnují autoři knih o C++ spíše výjimečně. Američany a nangličany to příliš nezajímá, neboť počítače hovoří převážně jejich jazykem, a těch několik drobností, v nichž se odchylují, většinou nepůsobí problému. Je příznačné, že první podrobnější zmínky o třídě locale jsem našel v knize německého autora. (V mnoha ohledech šlo ovšem o knihu velmi špatnou, a proto ji zde neuvádím.)

Podrobný výklad lze najít v anglicky psané knize [2], jejímž autorem je také Němec.

Dobrým zdrojem informací je také kniha B. Stroustrupa [1]. (Autor je Dán.) V dodatku D najdete rozsáhlou kapitolu věnovanou této problematice. Musí ovšem jít o pozdější tisky třetího vydání nebo o vydání označené Special Edition. Druhé vydání, které bylo přeloženo do češtiny, tyto informace neobsahuje. Neobsahují je ani první tisku třetího vydání z r. 1997; lze si je ale stáhnout ve formátu PDF z adresy http://www.research.att.com/~bs/3rd_loc.pdf.

Miroslav Virius

 

Odkazy

[1] B. Stroustrup: The C++ Programming Language. Addison-Wesley 2000. Dodatek D. (Pouze v novějších tiscích 3. vydání a ve vydání označeném Special Edition. Lze také stáhnout ve formátu PDF ze stránky http://www.research.att.com/~bs/3rd_loc.pdf.
[2] N. M. Josuttis: The C++ Standard Library. A Tutorial and Reference. Addison-Wesley 2000.

 

Pokud vás problematika Internacionalizace a lokalizace v C++ zaujala, napište nám na adresu chipcd@vogel.cz o její pokračování. Očekáváme také vaše náměty a připomínky.