1 /********************************************************************** 2 3 Audacity: A Digital Audio Editor 4 5 @file TranslatableString.h 6 7 Paul Licameli split from Types.h 8 9 **********************************************************************/ 10 11 #ifndef __AUDACITY_TRANSLATABLE_STRING__ 12 #define __AUDACITY_TRANSLATABLE_STRING__ 13 14 #include <stddef.h> // for size_t 15 #include <functional> 16 #include <wx/string.h> 17 18 class Identifier; 19 20 #include <vector> 21 22 //! Holds a msgid for the translation catalog; may also bind format arguments 23 /*! 24 Different string-valued accessors for the msgid itself, and for the 25 user-visible translation with substitution of captured format arguments. 26 Also an accessor for format substitution into the English msgid, for debug- 27 only outputs. 28 The msgid should be used only in unusual cases and the translation more often 29 30 Implicit conversions to and from wxString are intentionally disabled 31 */ 32 class STRINGS_API TranslatableString { 33 enum class Request; 34 template< size_t N > struct PluralTemp; 35 36 public: 37 //! A special string value that will have no screen reader pronunciation 38 static const TranslatableString Inaudible; 39 40 //! A multi-purpose function, depending on the enum argument 41 /*! the string 42 argument is unused in some cases 43 If there is no function, defaults are empty context string, no plurals, 44 and no substitutions */ 45 using Formatter = std::function< wxString(const wxString &, Request) >; 46 TranslatableString()47 TranslatableString() {} 48 49 /*! Supply {} for the second argument to cause lookup of the msgid with 50 empty context string (default context) rather than the null context */ TranslatableString(wxString str,Formatter formatter)51 explicit TranslatableString( wxString str, Formatter formatter ) 52 : mFormatter{ std::move(formatter) } 53 { 54 mMsgid.swap( str ); 55 } 56 57 // copy and move 58 TranslatableString( const TranslatableString & ) = default; 59 TranslatableString &operator=( const TranslatableString & ) = default; TranslatableString(TranslatableString && str)60 TranslatableString( TranslatableString && str ) 61 : mFormatter( std::move( str.mFormatter ) ) 62 { 63 mMsgid.swap( str.mMsgid ); 64 } 65 TranslatableString &operator=( TranslatableString &&str ) 66 { 67 mFormatter = std::move( str.mFormatter ); 68 mMsgid.swap( str.mMsgid ); 69 return *this; 70 } 71 empty()72 bool empty() const { return mMsgid.empty(); } 73 74 //! MSGID is the English lookup key in the catalog, not necessarily for user's eyes if locale is some other. 75 /*! The MSGID might not be all the information TranslatableString holds. 76 This is a deliberately ugly-looking function name. Use with caution. */ 77 Identifier MSGID() const; 78 Translation()79 wxString Translation() const { return DoFormat( false ); } 80 81 //! Format as an English string for debugging logs and developers' eyes, not for end users Debug()82 wxString Debug() const { return DoFormat( true ); } 83 84 //! Warning: comparison of msgids only, which is not all of the information! 85 /*! This operator makes it easier to define a std::unordered_map on TranslatableStrings */ 86 friend bool operator == ( 87 const TranslatableString &x, const TranslatableString &y) 88 { return x.mMsgid == y.mMsgid; } 89 90 friend bool operator != ( 91 const TranslatableString &x, const TranslatableString &y) 92 { return !(x == y); } 93 94 //! Returns true if context is NullContextFormatter 95 bool IsVerbatim() const; 96 97 //! Capture variadic format arguments (by copy) when there is no plural. 98 /*! The substitution is computed later in a call to Translate() after msgid is 99 looked up in the translation catalog. 100 Any format arguments that are also of type TranslatableString will be 101 translated too at substitution time, for non-debug formatting */ 102 template< typename... Args > Format(Args &&...args)103 TranslatableString &Format( Args &&...args ) & 104 { 105 auto prevFormatter = mFormatter; 106 this->mFormatter = [prevFormatter, args...] 107 (const wxString &str, Request request) -> wxString { 108 switch ( request ) { 109 case Request::Context: 110 return TranslatableString::DoGetContext( prevFormatter ); 111 case Request::Format: 112 case Request::DebugFormat: 113 default: { 114 bool debug = request == Request::DebugFormat; 115 return wxString::Format( 116 TranslatableString::DoSubstitute( 117 prevFormatter, 118 str, TranslatableString::DoGetContext( prevFormatter ), 119 debug ), 120 TranslatableString::TranslateArgument( args, debug )... 121 ); 122 } 123 } 124 }; 125 return *this; 126 } 127 template< typename... Args > Format(Args &&...args)128 TranslatableString &&Format( Args &&...args ) && 129 { 130 return std::move( Format( std::forward<Args>(args)... ) ); 131 } 132 133 //! Choose a non-default and non-null disambiguating context for lookups 134 /*! This is meant to be the first of chain-call modifications of the 135 TranslatableString object; it will destroy any previously captured 136 information */ Context(const wxString & context)137 TranslatableString &Context( const wxString &context ) & 138 { 139 this->mFormatter = [context] 140 (const wxString &str, Request request) -> wxString { 141 switch ( request ) { 142 case Request::Context: 143 return context; 144 case Request::DebugFormat: 145 return DoSubstitute( {}, str, context, true ); 146 case Request::Format: 147 default: 148 return DoSubstitute( {}, str, context, false ); 149 } 150 }; 151 return *this; 152 } Context(const wxString & context)153 TranslatableString &&Context( const wxString &context ) && 154 { 155 return std::move( Context( context ) ); 156 } 157 158 //! Append another translatable string 159 /*! lookup of msgids for 160 this and for the argument are both delayed until Translate() is invoked 161 on this, and then the formatter concatenates the translations */ 162 TranslatableString &Join( 163 TranslatableString arg, const wxString &separator = {} ) &; 164 TranslatableString &&Join( 165 TranslatableString arg, const wxString &separator = {} ) && 166 { return std::move( Join( std::move(arg), separator ) ); } 167 168 TranslatableString &operator +=( TranslatableString arg ) 169 { 170 Join( std::move( arg ) ); 171 return *this; 172 } 173 174 //! Implements the XP macro 175 /*! That macro specifies a second msgid, a list 176 of format arguments, and which of those format arguments selects among 177 messages; the translated strings to select among, depending on language, 178 might actually be more or fewer than two. See Internat.h. */ 179 template< size_t N > Plural(const wxString & pluralStr)180 PluralTemp< N > Plural( const wxString &pluralStr ) && 181 { 182 return PluralTemp< N >{ *this, pluralStr }; 183 } 184 185 /*! Translated strings may still contain menu hot-key codes (indicated by &) 186 that wxWidgets interprets, and also trailing ellipses, that should be 187 removed for other uses. */ 188 enum StripOptions : unsigned { 189 // Values to be combined with bitwise OR 190 MenuCodes = 0x1, 191 Ellipses = 0x2, 192 }; 193 TranslatableString &Strip( unsigned options = MenuCodes ) &; 194 TranslatableString &&Strip( unsigned options = MenuCodes ) && 195 { return std::move( Strip( options ) ); } 196 197 //! non-mutating, constructs another TranslatableString object 198 TranslatableString Stripped( unsigned options = MenuCodes ) const 199 { return TranslatableString{ *this }.Strip( options ); } 200 StrippedTranslation()201 wxString StrippedTranslation() const { return Stripped().Translation(); } 202 203 private: 204 static const Formatter NullContextFormatter; 205 206 //! Construct a TranslatableString that does no translation but passes str verbatim TranslatableString(wxString str)207 explicit TranslatableString( wxString str ) 208 : mFormatter{ NullContextFormatter } 209 { 210 mMsgid.swap( str ); 211 } 212 213 friend TranslatableString Verbatim( wxString str ); 214 215 enum class Request { 216 Context, //!< return a disambiguating context string 217 Format, //!< Given the msgid, format the string for end users 218 DebugFormat, //!< Given the msgid, format the string for developers 219 }; 220 221 static const wxChar *const NullContextName; 222 friend std::hash< TranslatableString >; 223 224 static wxString DoGetContext( const Formatter &formatter ); 225 static wxString DoSubstitute( 226 const Formatter &formatter, 227 const wxString &format, const wxString &context, bool debug ); DoFormat(bool debug)228 wxString DoFormat( bool debug ) const 229 { return DoSubstitute( 230 mFormatter, mMsgid, DoGetContext(mFormatter), debug ); } 231 232 static wxString DoChooseFormat( 233 const Formatter &formatter, 234 const wxString &singular, const wxString &plural, unsigned nn, bool debug ); 235 TranslateArgument(const T & arg,bool)236 template< typename T > static const T &TranslateArgument( const T &arg, bool ) 237 { return arg; } 238 //! This allows you to wrap arguments of Format in std::cref 239 /*! (So that they are captured (as if) by reference rather than by value) */ 240 template< typename T > static auto TranslateArgument( 241 const std::reference_wrapper<T> &arg, bool debug ) 242 -> decltype( 243 TranslatableString::TranslateArgument( arg.get(), debug ) ) 244 { return TranslatableString::TranslateArgument( arg.get(), debug ); } TranslateArgument(const TranslatableString & arg,bool debug)245 static wxString TranslateArgument( const TranslatableString &arg, bool debug ) 246 { return arg.DoFormat( debug ); } 247 248 template< size_t N > struct PluralTemp{ 249 TranslatableString &ts; 250 const wxString &pluralStr; 251 template< typename... Args > operatorPluralTemp252 TranslatableString &&operator()( Args&&... args ) 253 { 254 // Pick from the pack the argument that specifies number 255 auto selector = 256 std::template get< N >( std::forward_as_tuple( args... ) ); 257 // We need an unsigned value. Guard against negative values. 258 auto nn = static_cast<unsigned>( 259 std::max<unsigned long long>( 0, selector ) 260 ); 261 auto plural = this->pluralStr; 262 auto prevFormatter = this->ts.mFormatter; 263 this->ts.mFormatter = [prevFormatter, plural, nn, args...] 264 (const wxString &str, Request request) -> wxString { 265 switch ( request ) { 266 case Request::Context: 267 return TranslatableString::DoGetContext( prevFormatter ); 268 case Request::Format: 269 case Request::DebugFormat: 270 default: 271 { 272 bool debug = request == Request::DebugFormat; 273 return wxString::Format( 274 TranslatableString::DoChooseFormat( 275 prevFormatter, str, plural, nn, debug ), 276 TranslatableString::TranslateArgument( args, debug )... 277 ); 278 } 279 } 280 }; 281 return std::move(ts); 282 } 283 }; 284 285 wxString mMsgid; 286 Formatter mFormatter; 287 }; 288 289 inline TranslatableString operator +( 290 TranslatableString x, TranslatableString y ) 291 { 292 return std::move(x += std::move(y)); 293 } 294 295 using TranslatableStrings = std::vector<TranslatableString>; 296 297 //! For using std::unordered_map on TranslatableString 298 /*! Note: hashing on msgids only, which is not all of the information */ 299 namespace std 300 { 301 template<> struct hash< TranslatableString > { 302 size_t operator () (const TranslatableString &str) const // noexcept 303 { 304 const wxString &stdstr = str.mMsgid.ToStdWstring(); // no allocations, a cheap fetch 305 using Hasher = hash< wxString >; 306 return Hasher{}( stdstr ); 307 } 308 }; 309 } 310 311 //! Allow TranslatableString to work with shift output operators 312 template< typename Sink > 313 inline Sink &operator <<( Sink &sink, const TranslatableString &str ) 314 { 315 return sink << str.Translation(); 316 } 317 318 //! Require calls to the one-argument constructor to go through this distinct global function name. 319 /*! This makes it easier to locate and 320 review the uses of this function, separately from the uses of the type. */ 321 inline TranslatableString Verbatim( wxString str ) 322 { return TranslatableString( std::move( str ) ); } 323 324 #endif 325