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