diff --git a/docs/changes.txt b/docs/changes.txt index 45f8609b99..f465e50b0d 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -445,6 +445,7 @@ All: - Correct wxSocket::Peek() to not block (Anders Larsen). - Added IEC and SI units support to GetHumanReadableSize() (Julien Weinzorn). - Add convenient wxString::ToStd{String,Wstring}() helpers. +- Added wxTranslations class to allow localization without changing locale. Unix: diff --git a/include/wx/intl.h b/include/wx/intl.h index a7b4dd133c..f14a0a6a64 100644 --- a/include/wx/intl.h +++ b/include/wx/intl.h @@ -19,6 +19,10 @@ #include "wx/defs.h" #include "wx/string.h" +#if !wxUSE_UNICODE + #include "wx/hashmap.h" +#endif + // Make wxLayoutDirection enum available without need for wxUSE_INTL so wxWindow, wxApp // and other classes are not distrubed by wxUSE_INTL @@ -57,6 +61,7 @@ enum wxLayoutDirection // forward decls // ---------------------------------------------------------------------------- +class WXDLLIMPEXP_FWD_BASE wxTranslationsLoader; class WXDLLIMPEXP_FWD_BASE wxLocale; class WXDLLIMPEXP_FWD_BASE wxLanguageInfoArray; class wxMsgCatalog; @@ -355,6 +360,111 @@ struct WXDLLIMPEXP_BASE wxLanguageInfo inline wxString wxLanguageInfo::GetLocaleName() const { return CanonicalName; } #endif // !__WXMSW__ +// ---------------------------------------------------------------------------- +// wxTranslations: message catalogs +// ---------------------------------------------------------------------------- + +// this class allows to get translations for strings +class WXDLLIMPEXP_BASE wxTranslations +{ +public: + wxTranslations(); + ~wxTranslations(); + + // returns current translations object, may return NULL + static wxTranslations *Get(); + // sets current translations object (takes ownership; may be NULL) + static void Set(wxTranslations *t); + + // changes loader to non-default one; takes ownership of 'loader' + void SetLoader(wxTranslationsLoader *loader); + + void SetLanguage(wxLanguage lang); + void SetLanguage(const wxString& lang); + + // add standard wxWidgets catalog ("wxstd") + bool AddStdCatalog(); + + // add catalog with given domain name and language, looking it up via + // wxTranslationsLoader + bool AddCatalog(const wxString& domain); + bool AddCatalog(const wxString& domain, wxLanguage msgIdLanguage); +#if !wxUSE_UNICODE + bool AddCatalog(const wxString& domain, + wxLanguage msgIdLanguage, + const wxString& msgIdCharset); +#endif + + // check if the given catalog is loaded + bool IsLoaded(const wxString& domain) const; + + // load catalog data directly from file + bool LoadCatalogFile(const wxString& filename, + const wxString& domain = wxEmptyString); + + // access to translations + const wxString& GetString(const wxString& origString, + const wxString& domain = wxEmptyString) const; + const wxString& GetString(const wxString& origString, + const wxString& origString2, + size_t n, + const wxString& domain = wxEmptyString) const; + + wxString GetHeaderValue(const wxString& header, + const wxString& domain = wxEmptyString) const; + + // this is hack to work around a problem with wxGetTranslation() which + // returns const wxString& and not wxString, so when it returns untranslated + // string, it needs to have a copy of it somewhere + static const wxString& GetUntranslatedString(const wxString& str); + +private: + // find best translation for given domain + wxString ChooseLanguageForDomain(const wxString& domain, + const wxString& msgIdLang); + + // find catalog by name in a linked list, return NULL if !found + wxMsgCatalog *FindCatalog(const wxString& domain) const; + + // same as Set(), without taking ownership; only for wxLocale + static void SetNonOwned(wxTranslations *t); + friend class wxLocale; + +private: + wxString m_lang; + wxTranslationsLoader *m_loader; + + wxMsgCatalog *m_pMsgCat; // pointer to linked list of catalogs + +#if !wxUSE_UNICODE + wxStringToStringHashMap m_msgIdCharset; +#endif +}; + + +// abstraction of translations discovery and loading +class WXDLLIMPEXP_BASE wxTranslationsLoader +{ +public: + wxTranslationsLoader() {} + virtual ~wxTranslationsLoader() {} + + virtual bool LoadCatalog(wxTranslations *translations, + const wxString& domain, const wxString& lang) = 0; +}; + +// standard wxTranslationsLoader implementation, using filesystem +class WXDLLIMPEXP_BASE wxFileTranslationsLoader + : public wxTranslationsLoader +{ +public: + static void AddCatalogLookupPathPrefix(const wxString& prefix); + + virtual bool LoadCatalog(wxTranslations *translations, + const wxString& domain, const wxString& lang); +}; + + // ---------------------------------------------------------------------------- // wxLocaleCategory: the category of locale settings // ---------------------------------------------------------------------------- @@ -508,7 +618,8 @@ public: // (in this order). // // This only applies to subsequent invocations of AddCatalog()! - static void AddCatalogLookupPathPrefix(const wxString& prefix); + static void AddCatalogLookupPathPrefix(const wxString& prefix) + { wxFileTranslationsLoader::AddCatalogLookupPathPrefix(prefix); } // add a catalog: it's searched for in standard places (current directory // first, system one after), but the you may prepend additional directories to @@ -517,7 +628,10 @@ public: // The loaded catalog will be used for message lookup by GetString(). // // Returns 'true' if it was successfully loaded - bool AddCatalog(const wxString& domain); + bool AddCatalog(const wxString& domain) + { return m_translations.AddCatalog(domain); } + bool AddCatalog(const wxString& domain, wxLanguage msgIdLanguage) + { return m_translations.AddCatalog(domain, msgIdLanguage); } bool AddCatalog(const wxString& domain, wxLanguage msgIdLanguage, const wxString& msgIdCharset); @@ -525,7 +639,8 @@ public: static bool IsAvailable(int lang); // check if the given catalog is loaded - bool IsLoaded(const wxString& domain) const; + bool IsLoaded(const wxString& domain) const + { return m_translations.IsLoaded(domain); } // Retrieve the language info struct for the given language // @@ -536,6 +651,10 @@ public: // is not in database static wxString GetLanguageName(int lang); + // Returns ISO code ("canonical name") of language or empty string if the + // language is not in database + static wxString GetLanguageCanonicalName(int lang); + // Find the language for the given locale string which may be either a // canonical ISO 2 letter language code ("xx"), a language code followed by // the country code ("xx_XX") or a Windows full language name ("Xxxxx...") @@ -559,25 +678,35 @@ public: // // domains are searched in the last to first order, i.e. catalogs // added later override those added before. - virtual const wxString& GetString(const wxString& origString, - const wxString& domain = wxEmptyString) const; + const wxString& GetString(const wxString& origString, + const wxString& domain = wxEmptyString) const + { + return m_translations.GetString(origString, domain); + } // plural form version of the same: - virtual const wxString& GetString(const wxString& origString, - const wxString& origString2, - size_t n, - const wxString& domain = wxEmptyString) const; + const wxString& GetString(const wxString& origString, + const wxString& origString2, + size_t n, + const wxString& domain = wxEmptyString) const + { + return m_translations.GetString(origString, origString2, n, domain); + } // this is hack to work around a problem with wxGetTranslation() which // returns const wxString& and not wxString, so when it returns untranslated // string, it needs to have a copy of it somewhere - static const wxString& GetUntranslatedString(const wxString& str); + static const wxString& GetUntranslatedString(const wxString& str) + { return wxTranslations::GetUntranslatedString(str); } // Returns the current short name for the locale const wxString& GetName() const { return m_strShort; } // return the contents of .po file header wxString GetHeaderValue(const wxString& header, - const wxString& domain = wxEmptyString) const; + const wxString& domain = wxEmptyString) const + { + return m_translations.GetHeaderValue(header, domain); + } // These two methods are for internal use only. First one creates // ms_languagesDB if it doesn't already exist, second one destroys @@ -586,8 +715,9 @@ public: static void DestroyLanguagesDB(); private: - // find catalog by name in a linked list, return NULL if !found - wxMsgCatalog *FindCatalog(const wxString& domain) const; + bool DoInit(const wxString& name, + const wxString& shortName, + const wxString& locale); // copy default table of languages from global static array to // m_langugagesInfo, called by InitLanguagesDB @@ -603,10 +733,10 @@ private: const char *m_pszOldLocale; // previous locale from setlocale() wxLocale *m_pOldLocale; // previous wxLocale - wxMsgCatalog *m_pMsgCat; // pointer to linked list of catalogs - bool m_initialized; + wxTranslations m_translations; + static wxLanguageInfoArray *ms_languagesDB; wxDECLARE_NO_COPY_CLASS(wxLocale); @@ -623,28 +753,28 @@ extern WXDLLIMPEXP_BASE wxLocale* wxGetLocale(); inline const wxString& wxGetTranslation(const wxString& str, const wxString& domain = wxEmptyString) { - wxLocale *pLoc = wxGetLocale(); - if (pLoc) - return pLoc->GetString(str, domain); + wxTranslations *trans = wxTranslations::Get(); + if ( trans ) + return trans->GetString(str, domain); else // NB: this function returns reference to a string, so we have to keep // a copy of it somewhere - return wxLocale::GetUntranslatedString(str); + return wxTranslations::GetUntranslatedString(str); } inline const wxString& wxGetTranslation(const wxString& str1, const wxString& str2, size_t n, const wxString& domain = wxEmptyString) { - wxLocale *pLoc = wxGetLocale(); - if (pLoc) - return pLoc->GetString(str1, str2, n, domain); + wxTranslations *trans = wxTranslations::Get(); + if ( trans ) + return trans->GetString(str1, str2, n, domain); else // NB: this function returns reference to a string, so we have to keep // a copy of it somewhere return n == 1 - ? wxLocale::GetUntranslatedString(str1) - : wxLocale::GetUntranslatedString(str2); + ? wxTranslations::GetUntranslatedString(str1) + : wxTranslations::GetUntranslatedString(str2); } #else // !wxUSE_INTL diff --git a/interface/wx/intl.h b/interface/wx/intl.h index 31255ff190..ca251333dc 100644 --- a/interface/wx/intl.h +++ b/interface/wx/intl.h @@ -419,8 +419,8 @@ enum wxLocaleInfo wxLocale class encapsulates all language-dependent settings and is a generalization of the C locale concept. - In wxWidgets this class manages message catalogs which contain the translations - of the strings used to the current language. + In wxWidgets this class manages current locale. It also initializes and + activates wxTranslations object that manages message catalogs. For a list of the supported languages, please see ::wxLanguage enum values. These constants may be used to specify the language in wxLocale::Init and @@ -465,7 +465,7 @@ enum wxLocaleInfo @library{wxbase} @category{cfg} - @see @ref overview_i18n, @ref page_samples_internat, wxXLocale + @see @ref overview_i18n, @ref page_samples_internat, wxXLocale, wxTranslations */ class wxLocale { @@ -504,64 +504,23 @@ public: virtual ~wxLocale(); /** - Add a catalog for use with the current locale: it is searched for in standard - places (current directory first, then the system one), but you may also prepend - additional directories to the search path with AddCatalogLookupPathPrefix(). - - All loaded catalogs will be used for message lookup by GetString() for - the current locale. - - In this overload, @c msgid strings are assumed - to be in English and written only using 7-bit ASCII characters. - If you have to deal with non-English strings or 8-bit characters in the - source code, see the instructions in @ref overview_nonenglish. - - @return - @true if catalog was successfully loaded, @false otherwise (which might - mean that the catalog is not found or that it isn't in the correct format). + Calls wxTranslations::AddCatalog(const wxString&). */ bool AddCatalog(const wxString& domain); /** - Add a catalog for use with the current locale: it is searched for in standard - places (current directory first, then the system one), but you may also prepend - additional directories to the search path with AddCatalogLookupPathPrefix(). + Calls wxTranslations::AddCatalog(const wxString&, wxLanguage). + */ + bool AddCatalog(const wxString& domain, wxLanguage msgIdLanguage); - All loaded catalogs will be used for message lookup by GetString() for - the current locale. - - This overload takes two additional arguments, @a msgIdLanguage and @a msgIdCharset. - - @param domain - The catalog domain to add. - - @param msgIdLanguage - Specifies the language of "msgid" strings in source code - (i.e. arguments to GetString(), wxGetTranslation() and the _() macro). - It is used if AddCatalog() cannot find any catalog for current language: - if the language is same as source code language, then strings from source - code are used instead. - - @param msgIdCharset - Lets you specify the charset used for msgids in sources - in case they use 8-bit characters (e.g. German or French strings). - This argument has no effect in Unicode build, because literals in sources are - Unicode strings; you have to use compiler-specific method of setting the right - charset when compiling with Unicode. - - @return - @true if catalog was successfully loaded, @false otherwise (which might - mean that the catalog is not found or that it isn't in the correct format). + /** + Calls wxTranslations::AddCatalog(const wxString&, wxLanguage, const wxString&). */ bool AddCatalog(const wxString& domain, wxLanguage msgIdLanguage, const wxString& msgIdCharset); /** - Add a prefix to the catalog lookup path: the message catalog files will - be looked up under prefix/lang/LC_MESSAGES, prefix/lang and prefix - (in this order). - - This only applies to subsequent invocations of AddCatalog(). + Calls wxFileTranslationsLoader::AddCatalogLookupPathPrefix(). */ static void AddCatalogLookupPathPrefix(const wxString& prefix); @@ -596,12 +555,7 @@ public: wxString GetCanonicalName() const; /** - Returns the header value for header @a header. - The search for @a header is case sensitive. If an @a domain is passed, - this domain is searched. Else all domains will be searched until a - header has been found. - - The return value is the value of the header if found. Else this will be empty. + Calls wxTranslations::GetHeaderValue(). */ wxString GetHeaderValue(const wxString& header, const wxString& domain = wxEmptyString) const; @@ -634,6 +588,16 @@ public: */ static wxString GetLanguageName(int lang); + /** + Returns canonical name (see GetCanonicalName()) of the given language + or empty string if this language is unknown. + + See GetLanguageInfo() for a remark about special meaning of @c wxLANGUAGE_DEFAULT. + + @since 2.9.1 + */ + static wxString GetLanguageCanonicalName(int lang); + /** Returns the locale name as passed to the constructor or Init(). @@ -648,45 +612,13 @@ public: const wxString& GetName() const; /** - Retrieves the translation for a string in all loaded domains unless the @a domain - parameter is specified (and then only this catalog/domain is searched). - - Returns original string if translation is not available (in this case an - error message is generated the first time a string is not found; use - wxLogNull to suppress it). - - @remarks Domains are searched in the last to first order, i.e. catalogs - added later override those added before. + Calls wxTranslations::GetString(const wxString&, const wxString&) const. */ virtual const wxString& GetString(const wxString& origString, const wxString& domain = wxEmptyString) const; /** - Retrieves the translation for a string in all loaded domains unless the @a domain - parameter is specified (and then only this catalog/domain is searched). - - Returns original string if translation is not available (in this case an - error message is generated the first time a string is not found; use - wxLogNull to suppress it). - - This form is used when retrieving translation of string that has different - singular and plural form in English or different plural forms in some - other language. - It takes two extra arguments: @a origString parameter must contain the - singular form of the string to be converted. - - It is also used as the key for the search in the catalog. - The @a origString2 parameter is the plural form (in English). - - The parameter @a n is used to determine the plural form. - If no message catalog is found @a origString is returned if 'n == 1', - otherwise @a origString2. - - See GNU gettext manual for additional information on plural forms handling. - This method is called by the wxGetTranslation() function and _() macro. - - @remarks Domains are searched in the last to first order, i.e. catalogs - added later override those added before. + Calls wxTranslations::GetString(const wxString&, const wxString&, size_t, const wxString&) const. */ virtual const wxString& GetString(const wxString& origString, const wxString& origString2, size_t n, @@ -803,12 +735,7 @@ public: static bool IsAvailable(int lang); /** - Check if the given catalog is loaded, and returns @true if it is. - - According to GNU gettext tradition, each catalog normally corresponds to - 'domain' which is more or less the application name. - - @see AddCatalog() + Calls wxTranslations::IsLoaded(). */ bool IsLoaded(const wxString& domain) const; @@ -819,6 +746,303 @@ public: }; +/** + This class allows to get translations for strings. + + In wxWidgets this class manages message catalogs which contain the + translations of the strings used to the current language. Unlike wxLocale, + it isn't bound to locale. It can be used either independently of, or in + conjunction with wxLocale. In the latter case, you should initialize + wxLocale (which creates wxTranslations instance) first; in the former, you + need to create a wxTranslations object and Set() it manually. + + Only one wxTranslations instance is active at a time; it is set with the + Set() method and obtained using Get(). + + Unlike wxLocale, wxTranslations' primary mean of identifying language + is by its "canonical name", i.e. ISO 639 code, possibly combined with + ISO 3166 country code and additional modifiers (examples include + "fr", "en_GB" or "ca@valencia"; see wxLocale::GetCanonicalName() for + more information). This allows apps using wxTranslations API to use even + languages not recognized by the operating system or not listed in + wxLanguage enum. + + @since 2.9.1 + + @see wxLocale + */ +class wxTranslations +{ +public: + /// Constructor + wxTranslations(); + + /** + Returns current translations object, may return NULL. + + You must either call this early in app initialization code, or let + wxLocale do it for you. + */ + static wxTranslations *Get(); + + /** + Sets current translations object. + + Deletes previous translation object and takes ownership of @a t. + */ + static void Set(wxTranslations *t); + + /** + Changes loader use to read catalogs to a non-default one. + + Deletes previous loader and takes ownership of @a loader. + + @see wxTranslationsLoader, wxFileTranslationsLoader + */ + void SetLoader(wxTranslationsLoader *loader); + + /** + Sets translations language to use. + + wxLANGUAGE_DEFAULT has special meaning: best suitable translation, + given user's preference and available translations, will be used. + */ + void SetLanguage(wxLanguage lang); + + /** + Sets translations language to use. + + Empty @a lang string has the same meaning as wxLANGUAGE_DEFAULT in + SetLanguage(wxLanguage): best suitable translation, given user's + preference and available translations, will be used. + */ + void SetLanguage(const wxString& lang); + + /** + Add standard wxWidgets catalogs ("wxstd" and possible port-specific + catalogs). + + @return @true if a suitable catalog was found, @false otherwise + + @see AddCatalog() + */ + bool AddStdCatalog(); + + /** + Add a catalog for use with the current locale. + + By default, it is searched for in standard places (see + wxFileTranslationsLoader), but you may also prepend additional + directories to the search path with + wxFileTranslationsLoader::AddCatalogLookupPathPrefix(). + + All loaded catalogs will be used for message lookup by GetString() for + the current locale. + + In this overload, @c msgid strings are assumed + to be in English and written only using 7-bit ASCII characters. + If you have to deal with non-English strings or 8-bit characters in the + source code, see the instructions in @ref overview_nonenglish. + + @return + @true if catalog was successfully loaded, @false otherwise (which might + mean that the catalog is not found or that it isn't in the correct format). + */ + bool AddCatalog(const wxString& domain); + + /** + Same as AddCatalog(const wxString&), but takes an additional argument, + @a msgIdLanguage. + + @param domain + The catalog domain to add. + + @param msgIdLanguage + Specifies the language of "msgid" strings in source code + (i.e. arguments to GetString(), wxGetTranslation() and the _() macro). + It is used if AddCatalog() cannot find any catalog for current language: + if the language is same as source code language, then strings from source + code are used instead. + + @return + @true if catalog was successfully loaded, @false otherwise (which might + mean that the catalog is not found or that it isn't in the correct format). + */ + bool AddCatalog(const wxString& domain, wxLanguage msgIdLanguage); + + /** + Same as AddCatalog(const wxString&, wxLanguage), but takes two + additional arguments, @a msgIdLanguage and @a msgIdCharset. + + This overload is only available in non-Unicode build. + + @param domain + The catalog domain to add. + + @param msgIdLanguage + Specifies the language of "msgid" strings in source code + (i.e. arguments to GetString(), wxGetTranslation() and the _() macro). + It is used if AddCatalog() cannot find any catalog for current language: + if the language is same as source code language, then strings from source + code are used instead. + + @param msgIdCharset + Lets you specify the charset used for msgids in sources + in case they use 8-bit characters (e.g. German or French strings). + + @return + @true if catalog was successfully loaded, @false otherwise (which might + mean that the catalog is not found or that it isn't in the correct format). + */ + bool AddCatalog(const wxString& domain, + wxLanguage msgIdLanguage, + const wxString& msgIdCharset); + + /** + Check if the given catalog is loaded, and returns @true if it is. + + According to GNU gettext tradition, each catalog normally corresponds to + 'domain' which is more or less the application name. + + @see AddCatalog() + */ + bool IsLoaded(const wxString& domain) const; + + /** + Directly loads catalog from a file. + + It is caller's responsibility to ensure that the catalog contains + correct language. This function is primarily intended for + wxTranslationsLoader implementations. + + @param filename Name of the MO file to load. + @param domain Domain to load the translations into (typically + matches file's basename). + */ + bool LoadCatalogFile(const wxString& filename, + const wxString& domain = wxEmptyString); + + /** + Retrieves the translation for a string in all loaded domains unless the @a domain + parameter is specified (and then only this catalog/domain is searched). + + Returns original string if translation is not available (in this case an + error message is generated the first time a string is not found; use + wxLogNull to suppress it). + + @remarks Domains are searched in the last to first order, i.e. catalogs + added later override those added before. + */ + const wxString& GetString(const wxString& origString, + const wxString& domain = wxEmptyString) const; + + /** + Retrieves the translation for a string in all loaded domains unless the @a domain + parameter is specified (and then only this catalog/domain is searched). + + Returns original string if translation is not available (in this case an + error message is generated the first time a string is not found; use + wxLogNull to suppress it). + + This form is used when retrieving translation of string that has different + singular and plural form in English or different plural forms in some + other language. + It takes two extra arguments: @a origString parameter must contain the + singular form of the string to be converted. + + It is also used as the key for the search in the catalog. + The @a origString2 parameter is the plural form (in English). + + The parameter @a n is used to determine the plural form. + If no message catalog is found @a origString is returned if 'n == 1', + otherwise @a origString2. + + See GNU gettext manual for additional information on plural forms handling. + This method is called by the wxGetTranslation() function and _() macro. + + @remarks Domains are searched in the last to first order, i.e. catalogs + added later override those added before. + */ + const wxString& GetString(const wxString& origString, + const wxString& origString2, + size_t n, + const wxString& domain = wxEmptyString) const; + + /** + Returns the header value for header @a header. + The search for @a header is case sensitive. If an @a domain is passed, + this domain is searched. Else all domains will be searched until a + header has been found. + + The return value is the value of the header if found. Else this will be empty. + */ + wxString GetHeaderValue(const wxString& header, + const wxString& domain = wxEmptyString) const; +}; + + +/** + Abstraction of translations discovery and loading. + + This interface makes it possible to override wxWidgets' default catalogs + loading mechanism and load MO files from locations other than the + filesystem (e.g. embed them in executable). + + Implementations must implement the LoadCatalog() method. + + @see wxFileTranslationsLoader + */ +class wxTranslationsLoader +{ +public: + /// Constructor + wxTranslationsLoader() {} + + /** + Called to load requested catalog. + + If the catalog is found, LoadCatalog() should call LoadCatalogFile() + on @a translations to add the translation. + + @param translations wxTranslations requesting loading. + @param domain Domain to load. + @param lang Language to look for. This is "canonical name" + (see wxLocale::GetCanonicalName()), i.e. ISO 639 + code, possibly combined with country code or + additional modifiers (e.g. "fr", "en_GB" or + "ca@valencia"). + + @return @true on successful load, @false otherwise + */ + virtual bool LoadCatalog(wxTranslations *translations, + const wxString& domain, const wxString& lang) = 0; +}; + +/** + Standard wxTranslationsLoader implementation. + + This finds catalogs in the filesystem, using the standard Unix layout. + This is the default unless you change the loader with + wxTranslations::SetLoader(). + + Catalogs are searched for in standard places (current directory first, then + the system one), but you may also prepend additional directories to the + search path with AddCatalogLookupPathPrefix(). + */ +class wxFileTranslationsLoader : public wxTranslationsLoader +{ +public: + /** + Add a prefix to the catalog lookup path: the message catalog files will + be looked up under prefix/lang/LC_MESSAGES, prefix/lang and prefix + (in this order). + + This only applies to subsequent invocations of + wxTranslations::AddCatalog(). + */ + static void AddCatalogLookupPathPrefix(const wxString& prefix); +}; + // ============================================================================ @@ -892,7 +1116,7 @@ public: provided: the _() macro is defined to do the same thing as wxGetTranslation(). - This function calls wxLocale::GetString(). + This function calls wxTranslations::GetString(). @note This function is not suitable for literal strings in Unicode builds since the literal strings must be enclosed into _T() or wxT() macro diff --git a/src/common/intl.cpp b/src/common/intl.cpp index 61bf19eba5..9a1b3a4a25 100644 --- a/src/common/intl.cpp +++ b/src/common/intl.cpp @@ -853,8 +853,8 @@ public: wxMsgCatalogFile(); ~wxMsgCatalogFile(); - // load the catalog from disk (szDirPrefix corresponds to language) - bool Load(const wxString& szDirPrefix, const wxString& szName, + // load the catalog from disk + bool Load(const wxString& filename, wxPluralFormsCalculatorPtr& rPluralFormsCalculator); // fills the hash with string-translation pairs @@ -947,12 +947,13 @@ public: ~wxMsgCatalog(); #endif - // load the catalog from disk (szDirPrefix corresponds to language) - bool Load(const wxString& dirPrefix, const wxString& name, + // load the catalog from disk + bool Load(const wxString& filename, + const wxString& domain, const wxString& msgIdCharset); // get name of the catalog - wxString GetName() const { return m_name; } + wxString GetDomain() const { return m_domain; } // get the translated string: returns NULL if not found const wxString *GetString(const wxString& sz, size_t n = size_t(-1)) const; @@ -962,7 +963,7 @@ public: private: wxMessagesHash m_messages; // all messages in the catalog - wxString m_name; // name of the domain + wxString m_domain; // name of the domain #if !wxUSE_UNICODE // the conversion corresponding to this catalog charset if we installed it @@ -973,12 +974,6 @@ private: wxPluralFormsCalculatorPtr m_pluralFormsCalculator; }; -// ---------------------------------------------------------------------------- -// global variables -// ---------------------------------------------------------------------------- - -// the list of the directories to search for message catalog files -static wxArrayString gs_searchPrefixes; // ============================================================================ // implementation @@ -1047,7 +1042,7 @@ wxString wxLanguageInfo::GetLocaleName() const #endif // __WXMSW__ // ---------------------------------------------------------------------------- -// wxMsgCatalogFile class +// wxMsgCatalogFile clas // ---------------------------------------------------------------------------- wxMsgCatalogFile::wxMsgCatalogFile() @@ -1058,146 +1053,11 @@ wxMsgCatalogFile::~wxMsgCatalogFile() { } -// return the directories to search for message catalogs under the given -// prefix, separated by wxPATH_SEP -static -wxString GetMsgCatalogSubdirs(const wxString& prefix, const wxString& lang) -{ - // Search first in Unix-standard prefix/lang/LC_MESSAGES, then in - // prefix/lang and finally in just prefix. - // - // Note that we use LC_MESSAGES on all platforms and not just Unix, because - // it doesn't cost much to look into one more directory and doing it this - // way has two important benefits: - // a) we don't break compatibility with wx-2.6 and older by stopping to - // look in a directory where the catalogs used to be and thus silently - // breaking apps after they are recompiled against the latest wx - // b) it makes it possible to package app's support files in the same - // way on all target platforms - const wxString pathPrefix = wxFileName(prefix, lang).GetFullPath(); - - wxString searchPath; - searchPath.reserve(4*pathPrefix.length()); - searchPath << pathPrefix << wxFILE_SEP_PATH << "LC_MESSAGES" << wxPATH_SEP - << prefix << wxFILE_SEP_PATH << wxPATH_SEP - << pathPrefix; - - return searchPath; -} - -// construct the search path for the given language -static wxString GetFullSearchPath(const wxString& lang) -{ - // first take the entries explicitly added by the program - wxArrayString paths; - paths.reserve(gs_searchPrefixes.size() + 1); - size_t n, - count = gs_searchPrefixes.size(); - for ( n = 0; n < count; n++ ) - { - paths.Add(GetMsgCatalogSubdirs(gs_searchPrefixes[n], lang)); - } - - -#if wxUSE_STDPATHS - // then look in the standard location - const wxString stdp = wxStandardPaths::Get(). - GetLocalizedResourcesDir(lang, wxStandardPaths::ResourceCat_Messages); - - if ( paths.Index(stdp) == wxNOT_FOUND ) - paths.Add(stdp); -#endif // wxUSE_STDPATHS - - // last look in default locations -#ifdef __UNIX__ - // LC_PATH is a standard env var containing the search path for the .mo - // files - const char *pszLcPath = wxGetenv("LC_PATH"); - if ( pszLcPath ) - { - const wxString lcp = GetMsgCatalogSubdirs(pszLcPath, lang); - if ( paths.Index(lcp) == wxNOT_FOUND ) - paths.Add(lcp); - } - - // also add the one from where wxWin was installed: - wxString wxp = wxGetInstallPrefix(); - if ( !wxp.empty() ) - { - wxp = GetMsgCatalogSubdirs(wxp + wxS("/share/locale"), lang); - if ( paths.Index(wxp) == wxNOT_FOUND ) - paths.Add(wxp); - } -#endif // __UNIX__ - - - // finally construct the full search path - wxString searchPath; - searchPath.reserve(500); - count = paths.size(); - for ( n = 0; n < count; n++ ) - { - searchPath += paths[n]; - if ( n != count - 1 ) - searchPath += wxPATH_SEP; - } - - return searchPath; -} - // open disk file and read in it's contents -bool wxMsgCatalogFile::Load(const wxString& szDirPrefix, const wxString& szName, +bool wxMsgCatalogFile::Load(const wxString& filename, wxPluralFormsCalculatorPtr& rPluralFormsCalculator) { - wxCHECK_MSG( szDirPrefix.length() >= LEN_LANG, false, - "invalid language specification" ); - - wxString searchPath; - -#if wxUSE_FONTMAP - // first look for the catalog for this language and the current locale: - // notice that we don't use the system name for the locale as this would - // force us to install catalogs in different locations depending on the - // system but always use the canonical name - wxFontEncoding encSys = wxLocale::GetSystemEncoding(); - if ( encSys != wxFONTENCODING_SYSTEM ) - { - wxString fullname(szDirPrefix); - fullname << wxS('.') << wxFontMapperBase::GetEncodingName(encSys); - searchPath << GetFullSearchPath(fullname) << wxPATH_SEP; - } -#endif // wxUSE_FONTMAP - - - searchPath += GetFullSearchPath(szDirPrefix); - if ( szDirPrefix.length() > LEN_LANG && szDirPrefix[LEN_LANG] == wxS('_') ) - { - // also add just base locale name: for things like "fr_BE" (Belgium - // French) we should use fall back on plain "fr" if no Belgium-specific - // message catalogs exist - searchPath << wxPATH_SEP - << GetFullSearchPath(ExtractLang(szDirPrefix)); - } - - wxLogTrace(TRACE_I18N, wxS("Looking for \"%s.mo\" in search path \"%s\""), - szName, searchPath); - - wxFileName fn(szName); - fn.SetExt(wxS("mo")); - - wxString strFullName; - if ( !wxFindFileInPath(&strFullName, searchPath, fn.GetFullPath()) ) - { - wxLogVerbose(_("catalog file for domain '%s' not found."), szName); - wxLogTrace(TRACE_I18N, wxS("Catalog \"%s.mo\" not found"), szName); - return false; - } - - // open file and read its data - wxLogVerbose(_("using catalog '%s' from '%s'."), szName, strFullName.c_str()); - wxLogTrace(TRACE_I18N, wxS("Using catalog \"%s\"."), strFullName.c_str()); - - wxFile fileMsg(strFullName); + wxFile fileMsg(filename); if ( !fileMsg.IsOpened() ) return false; @@ -1230,7 +1090,7 @@ bool wxMsgCatalogFile::Load(const wxString& szDirPrefix, const wxString& szName, if ( !bValid ) { // it's either too short or has incorrect magic number - wxLogWarning(_("'%s' is not a valid message catalog."), strFullName.c_str()); + wxLogWarning(_("'%s' is not a valid message catalog."), filename.c_str()); return false; } @@ -1424,14 +1284,15 @@ wxMsgCatalog::~wxMsgCatalog() } #endif // !wxUSE_UNICODE -bool wxMsgCatalog::Load(const wxString& dirPrefix, const wxString& name, +bool wxMsgCatalog::Load(const wxString& filename, + const wxString& domain, const wxString& msgIdCharset) { wxMsgCatalogFile file; - m_name = name; + m_domain = domain; - if ( !file.Load(dirPrefix, name, m_pluralFormsCalculator) ) + if ( !file.Load(filename, m_pluralFormsCalculator) ) return false; if ( !file.FillHash(m_messages, msgIdCharset) ) @@ -1465,6 +1326,504 @@ const wxString *wxMsgCatalog::GetString(const wxString& str, size_t n) const return NULL; } + +// ---------------------------------------------------------------------------- +// wxTranslations +// ---------------------------------------------------------------------------- + +namespace +{ + +wxTranslations *gs_translations = NULL; +bool gs_translationsOwned = false; + +} // anonymous namespace + + +/*static*/ +wxTranslations *wxTranslations::Get() +{ + return gs_translations; +} + +/*static*/ +void wxTranslations::Set(wxTranslations *t) +{ + if ( gs_translationsOwned ) + delete gs_translations; + gs_translations = t; + gs_translationsOwned = true; +} + +/*static*/ +void wxTranslations::SetNonOwned(wxTranslations *t) +{ + if ( gs_translationsOwned ) + delete gs_translations; + gs_translations = t; + gs_translationsOwned = false; +} + + +wxTranslations::wxTranslations() +{ + m_pMsgCat = NULL; + m_loader = new wxFileTranslationsLoader; +} + + +wxTranslations::~wxTranslations() +{ + delete m_loader; + + // free catalogs memory + wxMsgCatalog *pTmpCat; + while ( m_pMsgCat != NULL ) + { + pTmpCat = m_pMsgCat; + m_pMsgCat = m_pMsgCat->m_pNext; + delete pTmpCat; + } +} + + +void wxTranslations::SetLoader(wxTranslationsLoader *loader) +{ + wxCHECK_RET( loader, "loader can't be NULL" ); + + delete m_loader; + m_loader = loader; +} + + +void wxTranslations::SetLanguage(wxLanguage lang) +{ + if ( lang == wxLANGUAGE_DEFAULT ) + SetLanguage(""); + else + SetLanguage(wxLocale::GetLanguageCanonicalName(lang)); +} + +void wxTranslations::SetLanguage(const wxString& lang) +{ + m_lang = lang; +} + + +bool wxTranslations::AddStdCatalog() +{ + if ( !AddCatalog(wxS("wxstd")) ) + return false; + + // there may be a catalog with toolkit specific overrides, it is not + // an error if this does not exist + wxString port(wxPlatformInfo::Get().GetPortIdName()); + if ( !port.empty() ) + { + AddCatalog(port.BeforeFirst(wxS('/')).MakeLower()); + } + + return true; +} + + +bool wxTranslations::AddCatalog(const wxString& domain) +{ + return AddCatalog(domain, wxLANGUAGE_ENGLISH_US); +} + +#if !wxUSE_UNICODE +bool wxTranslations::AddCatalog(const wxString& domain, + wxLanguage msgIdLanguage, + const wxString& msgIdCharset) +{ + m_msgIdCharset[domain] = msgIdCharset; + return AddCatalog(domain, msgIdLanguage); +} +#endif // !wxUSE_UNICODE + +bool wxTranslations::AddCatalog(const wxString& domain, + wxLanguage msgIdLanguage) +{ + const wxString msgIdLang = wxLocale::GetLanguageCanonicalName(msgIdLanguage); + const wxString domain_lang = ChooseLanguageForDomain(domain, msgIdLang); + + if ( domain_lang.empty() ) + { + wxLogTrace(TRACE_I18N, + wxS("no suitable translation for domain '%s' found"), + domain); + return false; + } + + wxLogTrace(TRACE_I18N, + wxS("adding '%s' translation for domain '%s' (msgid language '%s')"), + domain_lang, domain, msgIdLang); + + // It is OK to not load catalog if the msgid language and m_language match, + // in which case we can directly display the texts embedded in program's + // source code: + if ( msgIdLang == domain_lang ) + return true; + + wxCHECK_MSG( m_loader, false, "loader can't be NULL" ); + return m_loader->LoadCatalog(this, domain, domain_lang); +} + + +// check if the given catalog is loaded +bool wxTranslations::IsLoaded(const wxString& domain) const +{ + return FindCatalog(domain) != NULL; +} + + +bool wxTranslations::LoadCatalogFile(const wxString& filename, + const wxString& domain) +{ + wxMsgCatalog *pMsgCat = new wxMsgCatalog; + +#if wxUSE_UNICODE + const bool ok = pMsgCat->Load(filename, domain, wxEmptyString/*unused*/); +#else + const bool ok = pMsgCat->Load(filename, domain, + m_msgIdCharset[domain]); +#endif + + if ( !ok ) + { + // don't add it because it couldn't be loaded anyway + delete pMsgCat; + return false; + } + + // add it to the head of the list so that in GetString it will + // be searched before the catalogs added earlier + pMsgCat->m_pNext = m_pMsgCat; + m_pMsgCat = pMsgCat; + + return true; +} + + +wxString wxTranslations::ChooseLanguageForDomain(const wxString& WXUNUSED(domain), + const wxString& WXUNUSED(msgIdLang)) +{ + // explicitly set language should always be respected + if ( !m_lang.empty() ) + return m_lang; + + // TODO: if the default language is used, pick the best (by comparing + // available languages with user's preferences), instead of blindly + // trusting availability of system language translation + return wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage()); +} + + +namespace +{ +WX_DECLARE_HASH_SET(wxString, wxStringHash, wxStringEqual, + wxLocaleUntranslatedStrings); +} + +/* static */ +const wxString& wxTranslations::GetUntranslatedString(const wxString& str) +{ + static wxLocaleUntranslatedStrings s_strings; + + wxLocaleUntranslatedStrings::iterator i = s_strings.find(str); + if ( i == s_strings.end() ) + return *s_strings.insert(str).first; + + return *i; +} + + +const wxString& wxTranslations::GetString(const wxString& origString, + const wxString& domain) const +{ + return GetString(origString, origString, size_t(-1), domain); +} + +const wxString& wxTranslations::GetString(const wxString& origString, + const wxString& origString2, + size_t n, + const wxString& domain) const +{ + if ( origString.empty() ) + return GetUntranslatedString(origString); + + const wxString *trans = NULL; + wxMsgCatalog *pMsgCat; + + if ( !domain.empty() ) + { + pMsgCat = FindCatalog(domain); + + // does the catalog exist? + if ( pMsgCat != NULL ) + trans = pMsgCat->GetString(origString, n); + } + else + { + // search in all domains + for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext ) + { + trans = pMsgCat->GetString(origString, n); + if ( trans != NULL ) // take the first found + break; + } + } + + if ( trans == NULL ) + { + wxLogTrace + ( + TRACE_I18N, + "string \"%s\"%s not found in %slocale '%s'.", + origString, + ((long)n) != -1 ? wxString::Format("[%ld]", (long)n) : wxString(), + !domain.empty() ? wxString::Format("domain '%s' ", domain) : wxString(), + m_lang + ); + + if (n == size_t(-1)) + return GetUntranslatedString(origString); + else + return GetUntranslatedString(n == 1 ? origString : origString2); + } + + return *trans; +} + + +wxString wxTranslations::GetHeaderValue(const wxString& header, + const wxString& domain) const +{ + if ( header.empty() ) + return wxEmptyString; + + const wxString *trans = NULL; + wxMsgCatalog *pMsgCat; + + if ( !domain.empty() ) + { + pMsgCat = FindCatalog(domain); + + // does the catalog exist? + if ( pMsgCat == NULL ) + return wxEmptyString; + + trans = pMsgCat->GetString(wxEmptyString, (size_t)-1); + } + else + { + // search in all domains + for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext ) + { + trans = pMsgCat->GetString(wxEmptyString, (size_t)-1); + if ( trans != NULL ) // take the first found + break; + } + } + + if ( !trans || trans->empty() ) + return wxEmptyString; + + size_t found = trans->find(header); + if ( found == wxString::npos ) + return wxEmptyString; + + found += header.length() + 2 /* ': ' */; + + // Every header is separated by \n + + size_t endLine = trans->find(wxS('\n'), found); + size_t len = (endLine == wxString::npos) ? + wxString::npos : (endLine - found); + + return trans->substr(found, len); +} + + +// find catalog by name in a linked list, return NULL if !found +wxMsgCatalog *wxTranslations::FindCatalog(const wxString& domain) const +{ + // linear search in the linked list + wxMsgCatalog *pMsgCat; + for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext ) + { + if ( pMsgCat->GetDomain() == domain ) + return pMsgCat; + } + + return NULL; +} + +// ---------------------------------------------------------------------------- +// wxFileTranslationsLoader +// ---------------------------------------------------------------------------- + +namespace +{ + +// the list of the directories to search for message catalog files +wxArrayString gs_searchPrefixes; + +// return the directories to search for message catalogs under the given +// prefix, separated by wxPATH_SEP +wxString GetMsgCatalogSubdirs(const wxString& prefix, const wxString& lang) +{ + // Search first in Unix-standard prefix/lang/LC_MESSAGES, then in + // prefix/lang and finally in just prefix. + // + // Note that we use LC_MESSAGES on all platforms and not just Unix, because + // it doesn't cost much to look into one more directory and doing it this + // way has two important benefits: + // a) we don't break compatibility with wx-2.6 and older by stopping to + // look in a directory where the catalogs used to be and thus silently + // breaking apps after they are recompiled against the latest wx + // b) it makes it possible to package app's support files in the same + // way on all target platforms + const wxString pathPrefix = wxFileName(prefix, lang).GetFullPath(); + + wxString searchPath; + searchPath.reserve(4*pathPrefix.length()); + searchPath << pathPrefix << wxFILE_SEP_PATH << "LC_MESSAGES" << wxPATH_SEP + << prefix << wxFILE_SEP_PATH << wxPATH_SEP + << pathPrefix; + + return searchPath; +} + +// construct the search path for the given language +static wxString GetFullSearchPath(const wxString& lang) +{ + // first take the entries explicitly added by the program + wxArrayString paths; + paths.reserve(gs_searchPrefixes.size() + 1); + size_t n, + count = gs_searchPrefixes.size(); + for ( n = 0; n < count; n++ ) + { + paths.Add(GetMsgCatalogSubdirs(gs_searchPrefixes[n], lang)); + } + + +#if wxUSE_STDPATHS + // then look in the standard location + const wxString stdp = wxStandardPaths::Get(). + GetLocalizedResourcesDir(lang, wxStandardPaths::ResourceCat_Messages); + + if ( paths.Index(stdp) == wxNOT_FOUND ) + paths.Add(stdp); +#endif // wxUSE_STDPATHS + + // last look in default locations +#ifdef __UNIX__ + // LC_PATH is a standard env var containing the search path for the .mo + // files + const char *pszLcPath = wxGetenv("LC_PATH"); + if ( pszLcPath ) + { + const wxString lcp = GetMsgCatalogSubdirs(pszLcPath, lang); + if ( paths.Index(lcp) == wxNOT_FOUND ) + paths.Add(lcp); + } + + // also add the one from where wxWin was installed: + wxString wxp = wxGetInstallPrefix(); + if ( !wxp.empty() ) + { + wxp = GetMsgCatalogSubdirs(wxp + wxS("/share/locale"), lang); + if ( paths.Index(wxp) == wxNOT_FOUND ) + paths.Add(wxp); + } +#endif // __UNIX__ + + + // finally construct the full search path + wxString searchPath; + searchPath.reserve(500); + count = paths.size(); + for ( n = 0; n < count; n++ ) + { + searchPath += paths[n]; + if ( n != count - 1 ) + searchPath += wxPATH_SEP; + } + + return searchPath; +} + +} // anonymous namespace + + +void wxFileTranslationsLoader::AddCatalogLookupPathPrefix(const wxString& prefix) +{ + if ( gs_searchPrefixes.Index(prefix) == wxNOT_FOUND ) + { + gs_searchPrefixes.Add(prefix); + } + //else: already have it +} + + +bool wxFileTranslationsLoader::LoadCatalog(wxTranslations *translations, + const wxString& domain, + const wxString& lang) +{ + wxCHECK_MSG( lang.length() >= LEN_LANG, false, + "invalid language specification" ); + + wxString searchPath; + +#if wxUSE_FONTMAP + // first look for the catalog for this language and the current locale: + // notice that we don't use the system name for the locale as this would + // force us to install catalogs in different locations depending on the + // system but always use the canonical name + wxFontEncoding encSys = wxLocale::GetSystemEncoding(); + if ( encSys != wxFONTENCODING_SYSTEM ) + { + wxString fullname(lang); + fullname << wxS('.') << wxFontMapperBase::GetEncodingName(encSys); + searchPath << GetFullSearchPath(fullname) << wxPATH_SEP; + } +#endif // wxUSE_FONTMAP + + searchPath += GetFullSearchPath(lang); + if ( lang.length() > LEN_LANG && lang[LEN_LANG] == wxS('_') ) + { + // also add just base locale name: for things like "fr_BE" (Belgium + // French) we should use fall back on plain "fr" if no Belgium-specific + // message catalogs exist + searchPath << wxPATH_SEP + << GetFullSearchPath(ExtractLang(lang)); + } + + wxLogTrace(TRACE_I18N, wxS("Looking for \"%s.mo\" in search path \"%s\""), + domain, searchPath); + + wxFileName fn(domain); + fn.SetExt(wxS("mo")); + + wxString strFullName; + if ( !wxFindFileInPath(&strFullName, searchPath, fn.GetFullPath()) ) + { + wxLogVerbose(_("catalog file for domain '%s' not found."), domain); + wxLogTrace(TRACE_I18N, wxS("Catalog \"%s.mo\" not found"), domain); + return false; + } + + // open file and read its data + wxLogVerbose(_("using catalog '%s' from '%s'."), domain, strFullName.c_str()); + wxLogTrace(TRACE_I18N, wxS("Using catalog \"%s\"."), strFullName.c_str()); + + return translations->LoadCatalogFile(strFullName, domain); +} + + // ---------------------------------------------------------------------------- // wxLocale // ---------------------------------------------------------------------------- @@ -1496,8 +1855,8 @@ void wxLocale::DoCommonInit() m_pszOldLocale = NULL; m_pOldLocale = wxSetLocale(this); + wxTranslations::SetNonOwned(&m_translations); - m_pMsgCat = NULL; m_language = wxLANGUAGE_UNKNOWN; m_initialized = false; } @@ -1512,14 +1871,29 @@ bool wxLocale::Init(const wxString& name, #endif ) { - wxASSERT_MSG( !m_initialized, - wxS("you can't call wxLocale::Init more than once") ); - #if WXWIN_COMPATIBILITY_2_8 wxASSERT_MSG( bConvertEncoding, wxS("wxLocale::Init with bConvertEncoding=false is no longer supported, add charset to your catalogs") ); #endif + bool ret = DoInit(name, shortName, locale); + + // NB: don't use 'lang' here, 'language' may be wxLANGUAGE_DEFAULT + m_translations.SetLanguage(shortName); + + if ( bLoadDefault ) + m_translations.AddStdCatalog(); + + return ret; +} + +bool wxLocale::DoInit(const wxString& name, + const wxString& shortName, + const wxString& locale) +{ + wxASSERT_MSG( !m_initialized, + wxS("you can't call wxLocale::Init more than once") ); + m_initialized = true; m_strLocale = name; m_strShort = shortName; @@ -1560,26 +1934,7 @@ bool wxLocale::Init(const wxString& name, } } - // load the default catalog with wxWidgets standard messages - m_pMsgCat = NULL; - bool bOk = true; - if ( bLoadDefault ) - { - bOk = AddCatalog(wxS("wxstd")); - - // there may be a catalog with toolkit specific overrides, it is not - // an error if this does not exist - if ( bOk ) - { - wxString port(wxPlatformInfo::Get().GetPortIdName()); - if ( !port.empty() ) - { - AddCatalog(port.BeforeFirst(wxS('/')).MakeLower()); - } - } - } - - return bOk; + return true; } @@ -1818,8 +2173,7 @@ bool wxLocale::Init(int language, int flags) // this language } - if ( !Init(name, canonical, retloc, - (flags & wxLOCALE_LOAD_DEFAULT) != 0) ) + if ( !DoInit(name, canonical, retloc) ) { ret = false; } @@ -1827,21 +2181,16 @@ bool wxLocale::Init(int language, int flags) if (IsOk()) // setlocale() succeeded m_language = lang; + // NB: don't use 'lang' here, 'language' + m_translations.SetLanguage(wx_static_cast(wxLanguage, language)); + + if ( flags & wxLOCALE_LOAD_DEFAULT ) + m_translations.AddStdCatalog(); + return ret; #endif // !WX_NO_LOCALE_SUPPORT } - - -void wxLocale::AddCatalogLookupPathPrefix(const wxString& prefix) -{ - if ( gs_searchPrefixes.Index(prefix) == wxNOT_FOUND ) - { - gs_searchPrefixes.Add(prefix); - } - //else: already have it -} - /*static*/ int wxLocale::GetSystemLanguage() { CreateLanguagesDB(); @@ -2216,6 +2565,9 @@ const wxLanguageInfo *wxLocale::GetLanguageInfo(int lang) /* static */ wxString wxLocale::GetLanguageName(int lang) { + if ( lang == wxLANGUAGE_DEFAULT || lang == wxLANGUAGE_UNKNOWN ) + return wxEmptyString; + const wxLanguageInfo *info = GetLanguageInfo(lang); if ( !info ) return wxEmptyString; @@ -2223,6 +2575,19 @@ wxString wxLocale::GetLanguageName(int lang) return info->Description; } +/* static */ +wxString wxLocale::GetLanguageCanonicalName(int lang) +{ + if ( lang == wxLANGUAGE_DEFAULT || lang == wxLANGUAGE_UNKNOWN ) + return wxEmptyString; + + const wxLanguageInfo *info = GetLanguageInfo(lang); + if ( !info ) + return wxEmptyString; + else + return info->CanonicalName; +} + /* static */ const wxLanguageInfo *wxLocale::FindLanguageInfo(const wxString& locale) { @@ -2267,12 +2632,13 @@ wxString wxLocale::GetSysName() const // clean up wxLocale::~wxLocale() { - // free memory - wxMsgCatalog *pTmpCat; - while ( m_pMsgCat != NULL ) { - pTmpCat = m_pMsgCat; - m_pMsgCat = m_pMsgCat->m_pNext; - delete pTmpCat; + // restore old translations object + if ( wxTranslations::Get() == &m_translations ) + { + if ( m_pOldLocale ) + wxTranslations::SetNonOwned(&m_pOldLocale->m_translations); + else + wxTranslations::Set(NULL); } // restore old locale pointer @@ -2282,137 +2648,6 @@ wxLocale::~wxLocale() free((wxChar *)m_pszOldLocale); // const_cast } -// get the translation of given string in current locale -const wxString& wxLocale::GetString(const wxString& origString, - const wxString& domain) const -{ - return GetString(origString, origString, size_t(-1), domain); -} - -const wxString& wxLocale::GetString(const wxString& origString, - const wxString& origString2, - size_t n, - const wxString& domain) const -{ - if ( origString.empty() ) - return GetUntranslatedString(origString); - - const wxString *trans = NULL; - wxMsgCatalog *pMsgCat; - - if ( !domain.empty() ) - { - pMsgCat = FindCatalog(domain); - - // does the catalog exist? - if ( pMsgCat != NULL ) - trans = pMsgCat->GetString(origString, n); - } - else - { - // search in all domains - for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext ) - { - trans = pMsgCat->GetString(origString, n); - if ( trans != NULL ) // take the first found - break; - } - } - - if ( trans == NULL ) - { - wxLogTrace(TRACE_I18N, - wxS("string \"%s\"[%ld] not found in %slocale '%s'."), - origString, (long)n, - wxString::Format(wxS("domain '%s' "), domain).c_str(), - m_strLocale.c_str()); - - if (n == size_t(-1)) - return GetUntranslatedString(origString); - else - return GetUntranslatedString(n == 1 ? origString : origString2); - } - - return *trans; -} - -WX_DECLARE_HASH_SET(wxString, wxStringHash, wxStringEqual, - wxLocaleUntranslatedStrings); - -/* static */ -const wxString& wxLocale::GetUntranslatedString(const wxString& str) -{ - static wxLocaleUntranslatedStrings s_strings; - - wxLocaleUntranslatedStrings::iterator i = s_strings.find(str); - if ( i == s_strings.end() ) - return *s_strings.insert(str).first; - - return *i; -} - -wxString wxLocale::GetHeaderValue(const wxString& header, - const wxString& domain) const -{ - if ( header.empty() ) - return wxEmptyString; - - const wxString *trans = NULL; - wxMsgCatalog *pMsgCat; - - if ( !domain.empty() ) - { - pMsgCat = FindCatalog(domain); - - // does the catalog exist? - if ( pMsgCat == NULL ) - return wxEmptyString; - - trans = pMsgCat->GetString(wxEmptyString, (size_t)-1); - } - else - { - // search in all domains - for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext ) - { - trans = pMsgCat->GetString(wxEmptyString, (size_t)-1); - if ( trans != NULL ) // take the first found - break; - } - } - - if ( !trans || trans->empty() ) - return wxEmptyString; - - size_t found = trans->find(header); - if ( found == wxString::npos ) - return wxEmptyString; - - found += header.length() + 2 /* ': ' */; - - // Every header is separated by \n - - size_t endLine = trans->find(wxS('\n'), found); - size_t len = (endLine == wxString::npos) ? - wxString::npos : (endLine - found); - - return trans->substr(found, len); -} - - -// find catalog by name in a linked list, return NULL if !found -wxMsgCatalog *wxLocale::FindCatalog(const wxString& domain) const -{ - // linear search in the linked list - wxMsgCatalog *pMsgCat; - for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext ) - { - if ( pMsgCat->GetName() == domain ) - return pMsgCat; - } - - return NULL; -} // check if the given locale is provided by OS and C run time /* static */ @@ -2446,61 +2681,17 @@ bool wxLocale::IsAvailable(int lang) return true; } -// check if the given catalog is loaded -bool wxLocale::IsLoaded(const wxString& szDomain) const -{ - return FindCatalog(szDomain) != NULL; -} - -// add a catalog to our linked list -bool wxLocale::AddCatalog(const wxString& szDomain) -{ - return AddCatalog(szDomain, wxLANGUAGE_ENGLISH_US, wxEmptyString); -} - // add a catalog to our linked list bool wxLocale::AddCatalog(const wxString& szDomain, wxLanguage msgIdLanguage, const wxString& msgIdCharset) - { - wxCHECK_MSG( !m_strShort.empty(), false, "must initialize catalog first" ); - - - // It is OK to not load catalog if the msgid language and m_language match, - // in which case we can directly display the texts embedded in program's - // source code: - if ( msgIdLanguage == m_language ) - return true; - - - wxMsgCatalog *pMsgCat = new wxMsgCatalog; - - if ( pMsgCat->Load(m_strShort, szDomain, msgIdCharset) ) - { - // add it to the head of the list so that in GetString it will - // be searched before the catalogs added earlier - pMsgCat->m_pNext = m_pMsgCat; - m_pMsgCat = pMsgCat; - - return true; - } - - // don't add it because it couldn't be loaded anyway - delete pMsgCat; - - - // If there's no exact match, we may still get partial match where the - // (basic) language is same, but the country differs. For example, it's - // permitted to use en_US strings from sources even if m_language is en_GB: - const wxLanguageInfo *msgIdLangInfo = GetLanguageInfo(msgIdLanguage); - if ( msgIdLangInfo && - ExtractLang(msgIdLangInfo->CanonicalName) == ExtractLang(m_strShort) ) - { - return true; - } - - return false; +#if wxUSE_UNICODE + wxUnusedVar(msgIdCharset); + return m_translations.AddCatalog(szDomain, msgIdLanguage); +#else + return m_translations.AddCatalog(szDomain, msgIdLanguage, msgIdCharset); +#endif } // ---------------------------------------------------------------------------- @@ -3105,8 +3296,21 @@ class wxLocaleModule: public wxModule DECLARE_DYNAMIC_CLASS(wxLocaleModule) public: wxLocaleModule() {} - bool OnInit() { return true; } - void OnExit() { wxLocale::DestroyLanguagesDB(); } + + bool OnInit() + { + return true; + } + + void OnExit() + { + if ( gs_translationsOwned ) + delete gs_translations; + gs_translations = NULL; + gs_translationsOwned = true; + + wxLocale::DestroyLanguagesDB(); + } }; IMPLEMENT_DYNAMIC_CLASS(wxLocaleModule, wxModule)