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