]> git.saurik.com Git - wxWidgets.git/commitdiff
Split wxLocale into wxLocale and wxTranslations.
authorVáclav Slavík <vslavik@fastmail.fm>
Sat, 24 Apr 2010 07:06:18 +0000 (07:06 +0000)
committerVáclav Slavík <vslavik@fastmail.fm>
Sat, 24 Apr 2010 07:06:18 +0000 (07:06 +0000)
wxTranslations is for handling gettext translations. wxLocale manages
locale and provides compatiblity API for translations. Separating these
two loosely related tasks makes it possible to use translations into
languages not known by Windows or using localized GUI without all the
locales compilations.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@64117 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775

docs/changes.txt
include/wx/intl.h
interface/wx/intl.h
src/common/intl.cpp

index 45f8609b992b4441f03d95ae76ed8d782659cfd6..f465e50b0d381da3e7f99205e1c70768013e81a1 100644 (file)
@@ -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:
 
index a7b4dd133ced413d9df2650b2535182f95f99d0c..f14a0a6a642f05245bb186f04562f763967f8e3d 100644 (file)
 #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
index 31255ff1902f29f9b37fde50c4bb74a1a647900c..ca251333dc34600d0f3a66693085aef57128af58 100644 (file)
@@ -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().
-
-        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.
+        Calls wxTranslations::AddCatalog(const wxString&, wxLanguage).
+    */
+    bool AddCatalog(const wxString& domain, wxLanguage msgIdLanguage);
 
-        @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,
@@ -802,6 +734,170 @@ public:
     */
     static bool IsAvailable(int lang);
 
+    /**
+        Calls wxTranslations::IsLoaded().
+    */
+    bool IsLoaded(const wxString& domain) const;
+
+    /**
+        Returns @true if the locale could be set successfully.
+    */
+    bool IsOk() const;
+};
+
+
+/**
+    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.
 
@@ -809,16 +905,144 @@ public:
         'domain' which is more or less the application name.
 
         @see AddCatalog()
-    */
+     */
     bool IsLoaded(const wxString& domain) const;
 
     /**
-        Returns @true if the locale could be set successfully.
+        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.
     */
-    bool IsOk() const;
+    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
index 61bf19eba57d649cd683c55ed423e4de534a6706..9a1b3a4a25dd907623956ab826182e189cd8e3be 100644 (file)
@@ -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) )
@@ -1447,24 +1308,522 @@ const wxString *wxMsgCatalog::GetString(const wxString& str, size_t n) const
     {
         index = m_pluralFormsCalculator->evaluate(n);
     }
-    wxMessagesHash::const_iterator i;
-    if (index != 0)
+    wxMessagesHash::const_iterator i;
+    if (index != 0)
+    {
+        i = m_messages.find(wxString(str) + wxChar(index));   // plural
+    }
+    else
+    {
+        i = m_messages.find(str);
+    }
+
+    if ( i != m_messages.end() )
+    {
+        return &i->second;
+    }
+    else
+        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 )
     {
-        i = m_messages.find(wxString(str) + wxChar(index));   // plural
+        wxString fullname(lang);
+        fullname << wxS('.') << wxFontMapperBase::GetEncodingName(encSys);
+        searchPath << GetFullSearchPath(fullname) << wxPATH_SEP;
     }
-    else
+#endif // wxUSE_FONTMAP
+
+    searchPath += GetFullSearchPath(lang);
+    if ( lang.length() > LEN_LANG && lang[LEN_LANG] == wxS('_') )
     {
-        i = m_messages.find(str);
+        // 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));
     }
 
-    if ( i != m_messages.end() )
+    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()) )
     {
-        return &i->second;
+        wxLogVerbose(_("catalog file for domain '%s' not found."), domain);
+        wxLogTrace(TRACE_I18N, wxS("Catalog \"%s.mo\" not found"), domain);
+        return false;
     }
-    else
-        return NULL;
+
+    // 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,19 +2181,14 @@ bool wxLocale::Init(int language, int flags)
     if (IsOk()) // setlocale() succeeded
         m_language = lang;
 
-    return ret;
-#endif // !WX_NO_LOCALE_SUPPORT
-}
-
+    // NB: don't use 'lang' here, 'language'
+    m_translations.SetLanguage(wx_static_cast(wxLanguage, language));
 
+    if ( flags & wxLOCALE_LOAD_DEFAULT )
+        m_translations.AddStdCatalog();
 
-void wxLocale::AddCatalogLookupPathPrefix(const wxString& prefix)
-{
-    if ( gs_searchPrefixes.Index(prefix) == wxNOT_FOUND )
-    {
-        gs_searchPrefixes.Add(prefix);
-    }
-    //else: already have it
+    return ret;
+#endif // !WX_NO_LOCALE_SUPPORT
 }
 
 /*static*/ int wxLocale::GetSystemLanguage()
@@ -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)