diff --git a/include/audacity/ComponentInterface.h b/include/audacity/ComponentInterface.h index f96c07db0..3625d9bd0 100644 --- a/include/audacity/ComponentInterface.h +++ b/include/audacity/ComponentInterface.h @@ -68,12 +68,12 @@ public: // Allows implicit construction from an internal string re-used as a msgid ComponentInterfaceSymbol( const wxString &internal ) - : mInternal{ internal }, mMsgid{ internal } + : mInternal{ internal }, mMsgid{ internal, {} } {} // Allows implicit construction from an internal string re-used as a msgid ComponentInterfaceSymbol( const wxChar *msgid ) - : mInternal{ msgid }, mMsgid{ msgid } + : mInternal{ msgid }, mMsgid{ msgid, {} } {} // Two-argument version distinguishes internal from translatable string @@ -87,7 +87,7 @@ public: const wxString &Internal() const { return mInternal; } const TranslatableString &Msgid() const { return mMsgid; } - const wxString &Translation() const { return mMsgid.Translation(); } + const wxString Translation() const { return mMsgid.Translation(); } bool empty() const { return mInternal.empty(); } @@ -135,7 +135,7 @@ public: virtual wxString GetDescription() = 0; // non-virtual convenience function - const wxString& GetTranslatedName(); + const wxString GetTranslatedName(); // Parameters, if defined. false means no defined parameters. virtual bool DefineParams( ShuttleParams & WXUNUSED(S) ){ return false;}; diff --git a/include/audacity/Types.h b/include/audacity/Types.h index f11816d84..fd121fba7 100644 --- a/include/audacity/Types.h +++ b/include/audacity/Types.h @@ -43,6 +43,7 @@ #define __AUDACITY_TYPES_H__ #include +#include #include #include #include // for wxASSERT @@ -292,8 +293,8 @@ using CommandID = TaggedIdentifier< CommandIdTag, false >; using CommandIDs = std::vector; -// Holds a msgid for the translation catalog (and in future, might also hold a -// second, disambiguating context string) +// Holds a msgid for the translation catalog and may also hold a context string +// and a formatter closure that captures formatting arguments // // Two different wxString accessors -- one for the msgid itself, another for // the user-visible translation. The msgid should be used only in unusual cases @@ -302,10 +303,28 @@ using CommandIDs = std::vector; // Implicit conversions to and from wxString are intentionally disabled class TranslatableString : private wxString { public: + // A dual-purpose function + // Given the empty string, return a context string + // Given the translation of the msgid into the current locale, substitute + // format arguments into it + using Formatter = std::function< wxString(const wxString &) >; + + // This special formatter causes msgids to be used verbatim, not looked up + // in any catalog + static const Formatter NullContextFormatter; + TranslatableString() {} - explicit TranslatableString(const wxString &str) : wxString{ str } {} - explicit TranslatableString(const wxChar *str) : wxString{ str } {} + // Supply {} for the second argument to cause lookup of the msgid with + // empty context string + explicit TranslatableString( + const wxString &str, Formatter formatter = NullContextFormatter) + : wxString{ str }, mFormatter{ std::move(formatter) } {} + // Supply {} for the second argument to cause lookup of the msgid with + // empty context string + explicit TranslatableString( + const wxChar *str, Formatter formatter = NullContextFormatter) + : wxString{ str }, mFormatter{ std::move(formatter) } {} using wxString::empty; @@ -316,7 +335,7 @@ public: // This is a deliberately ugly-looking function name. Use with caution. Identifier MSGID() const { return Identifier{ *this }; } - const wxString &Translation() const; + wxString Translation() const; friend bool operator == ( const TranslatableString &x, const TranslatableString &y) @@ -326,8 +345,31 @@ public: const TranslatableString &x, const TranslatableString &y) { return !(x == y); } - // Future: may also store a domain and context, as if for dpgettext() + // Returns true if context is NullContextFormatter + bool IsVerbatim() const; + + // Capture variadic format arguments (by copy). The subsitution is + // computed later in a call to Translate() after msgid is looked up in the + // translation catalog. + template + TranslatableString&& Format( Args&&... args ) && + { + wxString context; + if ( this->mFormatter ) + context = this->mFormatter({}); + this->mFormatter = [context, args...](const wxString &str){ + if (str.empty()) + return context; + else + return wxString::Format( str, args... ); + }; + return std::move( *this ); + } + friend std::hash< TranslatableString >; +private: + + Formatter mFormatter; }; using TranslatableStrings = std::vector; diff --git a/src/Internat.cpp b/src/Internat.cpp index e1cbd72fc..b3f18b296 100644 --- a/src/Internat.cpp +++ b/src/Internat.cpp @@ -291,7 +291,35 @@ std::vector< Identifier > Identifier::split( wxChar separator ) const return { strings.begin(), strings.end() }; } -const wxString &TranslatableString::Translation() const +static const wxChar *const NullContextName = wxT("*"); + +const TranslatableString::Formatter +TranslatableString::NullContextFormatter { + [](const wxString & str) -> wxString { + if (str.empty()) + return NullContextName; + else + return str; + } +}; + +bool TranslatableString::IsVerbatim() const { - return wxGetTranslation(*this); + return mFormatter && mFormatter({}) == NullContextName; +} + +wxString TranslatableString::Translation() const +{ + wxString context; + if ( mFormatter ) + context = mFormatter({}); + + wxString result = (context == NullContextName) + ? *this + : wxGetTranslation( *this, {}, context ); + + if ( mFormatter ) + result = mFormatter( result ); + + return result; } diff --git a/src/Internat.h b/src/Internat.h index b57abe600..2a6f95028 100644 --- a/src/Internat.h +++ b/src/Internat.h @@ -29,7 +29,7 @@ extern AUDACITY_DLL_API const wxString& GetCustomSubstitution(const wxString& st #define _TS( s ) GetCustomSubstitution( s ) // Marks strings for extraction only... use .Translate() to translate. -#define XO(s) (TranslatableString{ wxT(s) }) +#define XO(s) (TranslatableString{ wxT(s), {} }) #ifdef _ #undef _ diff --git a/src/PluginManager.cpp b/src/PluginManager.cpp index 09e5c62ca..444ca7abe 100644 --- a/src/PluginManager.cpp +++ b/src/PluginManager.cpp @@ -3229,7 +3229,7 @@ int PluginManager::b64decode(const wxString &in, void *out) // These are defined out-of-line here, to keep ComponentInterface free of other // #include directives. -const wxString& ComponentInterface::GetTranslatedName() +const wxString ComponentInterface::GetTranslatedName() { return GetSymbol().Translation(); } diff --git a/src/effects/Equalization.cpp b/src/effects/Equalization.cpp index 96b2d76e3..f84ebb693 100644 --- a/src/effects/Equalization.cpp +++ b/src/effects/Equalization.cpp @@ -881,11 +881,11 @@ void EffectEqualization::PopulateOrExchange(ShuttleGui & S) mEQVals[i] = 0.; //S.SetSizerProportion(1); S.Prop(1) - .Name( TranslatableString{ + .Name( kThirdOct[i] < 1000. - ? wxString::Format(_("%d Hz"), (int)kThirdOct[i]) - : wxString::Format(_("%g kHz"), kThirdOct[i]/1000.) - } ) + ? XO("%d Hz").Format( (int)kThirdOct[i] ) + : XO("%g kHz").Format( kThirdOct[i]/1000. ) + ) .ConnectRoot( wxEVT_ERASE_BACKGROUND, &EffectEqualization::OnErase) .Position(wxEXPAND) diff --git a/src/effects/nyquist/Nyquist.cpp b/src/effects/nyquist/Nyquist.cpp index 8003746db..e84f4024e 100644 --- a/src/effects/nyquist/Nyquist.cpp +++ b/src/effects/nyquist/Nyquist.cpp @@ -186,6 +186,8 @@ NyquistEffect::NyquistEffect(const wxString &fName) } mFileName = fName; + // Use the file name verbatim as effect name. + // This is only a default name, overridden if we find a $name line: mName = TranslatableString{ mFileName.GetName() }; mFileModified = mFileName.GetModificationTime(); ParseFile(); @@ -223,7 +225,7 @@ VendorSymbol NyquistEffect::GetVendor() return XO("Audacity"); } - return TranslatableString{ mAuthor }; + return mAuthor; } wxString NyquistEffect::GetVersion() @@ -1576,9 +1578,10 @@ std::vector NyquistEffect::ParseChoice(const wxString & text) for (auto &choice : choices) { auto label = UnQuote(choice, true, &extra); if (extra.empty()) - results.push_back( { label } ); + results.push_back( TranslatableString{ label, {} } ); else - results.push_back( { extra, TranslatableString{ label } } ); + results.push_back( + { extra, TranslatableString{ label, {} } } ); } } else { @@ -1891,17 +1894,17 @@ bool NyquistEffect::Parse( // later looked up will lack the ... and will not be found. if (name.EndsWith(wxT("..."))) name = name.RemoveLast(3); - mName = TranslatableString{ name }; + mName = TranslatableString{ name, {} }; return true; } if (len >= 2 && tokens[0] == wxT("action")) { - mAction = TranslatableString{ UnQuote(tokens[1]) }; + mAction = TranslatableString{ UnQuote(tokens[1]), {} }; return true; } if (len >= 2 && tokens[0] == wxT("info")) { - mInfo = TranslatableString{ UnQuote(tokens[1]) }; + mInfo = TranslatableString{ UnQuote(tokens[1]), {} }; return true; } @@ -1951,18 +1954,19 @@ bool NyquistEffect::Parse( #endif if (len >= 2 && tokens[0] == wxT("author")) { - mAuthor = TranslatableString{ UnQuote(tokens[1]) }; + mAuthor = TranslatableString{ UnQuote(tokens[1]), {} }; return true; } if (len >= 2 && tokens[0] == wxT("release")) { // Value must be quoted if the release version string contains spaces. - mReleaseVersion = TranslatableString{ UnQuote(tokens[1]) }; + mReleaseVersion = + TranslatableString{ UnQuote(tokens[1]), {} }; return true; } if (len >= 2 && tokens[0] == wxT("copyright")) { - mCopyright = TranslatableString{ UnQuote(tokens[1]) }; + mCopyright = TranslatableString{ UnQuote(tokens[1]), {} }; return true; } diff --git a/src/prefs/QualityPrefs.cpp b/src/prefs/QualityPrefs.cpp index fa9e9a8a0..df0ad28c2 100644 --- a/src/prefs/QualityPrefs.cpp +++ b/src/prefs/QualityPrefs.cpp @@ -124,13 +124,7 @@ void QualityPrefs::GetNamesAndLabels() for (int i = 0; i < AudioIOBase::NumStandardRates; i++) { int iRate = AudioIOBase::StandardRates[i]; mSampleRateLabels.push_back(iRate); - mSampleRateNames.push_back( - // Composing strings for the choice control - // Note: the format string is localized, then substituted, - // and then the result is treated as if it were a msgid - // but really isn't in the translation catalog - /* i18n-hint Hertz, a unit of frequency */ - TranslatableString{ wxString::Format(_("%i Hz"), iRate) } ); + mSampleRateNames.push_back( XO("%i Hz").Format( iRate ) ); } mSampleRateNames.push_back(XO("Other...")); diff --git a/src/widgets/NumericTextCtrl.cpp b/src/widgets/NumericTextCtrl.cpp index e98e010c7..bc0529d64 100644 --- a/src/widgets/NumericTextCtrl.cpp +++ b/src/widgets/NumericTextCtrl.cpp @@ -709,7 +709,7 @@ NumericConverter::NumericConverter(Type type, void NumericConverter::ParseFormatString( const TranslatableString & untranslatedFormat) { - auto &format = untranslatedFormat.Translation(); + auto format = untranslatedFormat.Translation(); mPrefix = wxT(""); mFields.clear();