mirror of
https://github.com/cookiengineer/audacity
synced 2025-11-27 07:40:10 +01:00
New library lib-strings for Identifier and internationalization
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
# that it depends on
|
||||
set( LIBRARIES
|
||||
"lib-string-utils"
|
||||
lib-strings
|
||||
)
|
||||
|
||||
if ( ${_OPT}has_networking )
|
||||
|
||||
35
libraries/lib-strings/CMakeLists.txt
Normal file
35
libraries/lib-strings/CMakeLists.txt
Normal file
@@ -0,0 +1,35 @@
|
||||
#[[
|
||||
String wrapping types and internationalization support.
|
||||
|
||||
Identifier and specializations of TaggedIdentifier generate types of strings
|
||||
used for different internal purposes, not meant for users to see (except
|
||||
sometimes in macro programming), and the types do not implicitly interconvert.
|
||||
|
||||
TranslatableString holds the English msgid of a string meant for a user to see,
|
||||
and can also bind formatting arguments. It can generate a translation later,
|
||||
even if the global choice of locale changes during its lifetime. It does not
|
||||
implicitly interconvert with wxString.
|
||||
|
||||
This library depends only on the wxBase subset of wxWidgets.
|
||||
]]#
|
||||
|
||||
set( SOURCES
|
||||
Identifier.cpp
|
||||
Identifier.h
|
||||
Internat.cpp
|
||||
Internat.h
|
||||
Languages.cpp
|
||||
Languages.h
|
||||
TranslatableString.cpp
|
||||
TranslatableString.h
|
||||
UnusedStrings.h
|
||||
wxArrayStringEx.h
|
||||
wxArrayStringEx.cpp
|
||||
)
|
||||
set( LIBRARIES
|
||||
PRIVATE
|
||||
wxBase
|
||||
)
|
||||
audacity_library( lib-strings "${SOURCES}" "${LIBRARIES}"
|
||||
"" ""
|
||||
)
|
||||
32
libraries/lib-strings/Identifier.cpp
Normal file
32
libraries/lib-strings/Identifier.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
Identifier.cpp
|
||||
|
||||
Paul Licameli split from Internat.cpp
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include "Identifier.h"
|
||||
#include <wx/arrstr.h> // for wxSplit
|
||||
|
||||
Identifier::Identifier(
|
||||
std::initializer_list<Identifier> components, wxChar separator )
|
||||
{
|
||||
if( components.size() < 2 )
|
||||
{
|
||||
wxASSERT( false );
|
||||
return;
|
||||
}
|
||||
auto iter = components.begin(), end = components.end();
|
||||
value = (*iter++).value;
|
||||
while (iter != end)
|
||||
value += separator + (*iter++).value;
|
||||
}
|
||||
|
||||
std::vector< Identifier > Identifier::split( wxChar separator ) const
|
||||
{
|
||||
auto strings = ::wxSplit( value, separator );
|
||||
return { strings.begin(), strings.end() };
|
||||
}
|
||||
236
libraries/lib-strings/Identifier.h
Normal file
236
libraries/lib-strings/Identifier.h
Normal file
@@ -0,0 +1,236 @@
|
||||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
Identifier.h
|
||||
|
||||
Paul Licameli split from Types.h
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef __AUDACITY_IDENTIFIER__
|
||||
#define __AUDACITY_IDENTIFIER__
|
||||
|
||||
#include <vector>
|
||||
#include <wx/string.h>
|
||||
|
||||
//! An explicitly nonlocalized string, not meant for the user to see.
|
||||
/*! String manipulations are discouraged, other than splitting and joining on separator characters.
|
||||
Wherever GET is used to fetch the underlying wxString, there should be a comment explaining the need for it.
|
||||
*/
|
||||
class STRINGS_API Identifier
|
||||
{
|
||||
public:
|
||||
|
||||
Identifier() = default;
|
||||
|
||||
//! Allow implicit conversion to this class, but not from
|
||||
Identifier(const wxString &str) : value{ str } {}
|
||||
|
||||
//! Allow implicit conversion to this class, but not from
|
||||
Identifier(const wxChar *str) : value{ str } {}
|
||||
|
||||
//! Allow implicit conversion to this class, but not from
|
||||
Identifier(const char *str) : value{ str } {}
|
||||
|
||||
// Copy construction and assignment
|
||||
Identifier( const Identifier & ) = default;
|
||||
Identifier &operator = ( const Identifier& ) = default;
|
||||
|
||||
// Move construction and assignment
|
||||
Identifier( wxString && str )
|
||||
{ value.swap( str ); }
|
||||
Identifier( Identifier && id )
|
||||
{ swap( id ); }
|
||||
Identifier &operator= ( Identifier&& id )
|
||||
{
|
||||
if ( this != &id )
|
||||
value.clear(), swap( id );
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Implements moves
|
||||
void swap( Identifier &id ) { value.swap( id.value ); }
|
||||
|
||||
//! Convenience for building concatenated identifiers.
|
||||
/*! The list must have at least two members (so you don't easily circumvent the restrictions on
|
||||
interconversions intended in TaggedIdentifier below) */
|
||||
explicit
|
||||
Identifier(std::initializer_list<Identifier> components, wxChar separator);
|
||||
|
||||
bool empty() const { return value.empty(); }
|
||||
size_t size() const { return value.size(); }
|
||||
size_t length() const { return value.length(); }
|
||||
|
||||
//! Explicit conversion to wxString, meant to be ugly-looking and demanding of a comment why it's correct
|
||||
const wxString &GET() const { return value; }
|
||||
|
||||
std::vector< Identifier > split( wxChar separator ) const;
|
||||
|
||||
private:
|
||||
wxString value;
|
||||
};
|
||||
|
||||
//! Comparisons of Identifiers are case-sensitive
|
||||
inline bool operator == ( const Identifier &x, const Identifier &y )
|
||||
{ return x.GET() == y.GET(); }
|
||||
|
||||
inline bool operator != ( const Identifier &x, const Identifier &y )
|
||||
{ return !(x == y); }
|
||||
|
||||
inline bool operator < ( const Identifier &x, const Identifier &y )
|
||||
{ return x.GET() < y.GET(); }
|
||||
|
||||
inline bool operator > ( const Identifier &x, const Identifier &y )
|
||||
{ return y < x; }
|
||||
|
||||
inline bool operator <= ( const Identifier &x, const Identifier &y )
|
||||
{ return !(y < x); }
|
||||
|
||||
inline bool operator >= ( const Identifier &x, const Identifier &y )
|
||||
{ return !(x < y); }
|
||||
|
||||
namespace std
|
||||
{
|
||||
template<> struct hash< Identifier > {
|
||||
size_t operator () ( const Identifier &id ) const // noexcept
|
||||
{ return hash<wxString>{}( id.GET() ); }
|
||||
};
|
||||
}
|
||||
|
||||
//! This lets you pass Identifier into wxFileConfig::Read
|
||||
inline bool wxFromString(const wxString& str, Identifier *id)
|
||||
{ if (id) { *id = str; return true; } else return false; }
|
||||
|
||||
//! This lets you pass Identifier into wxFileConfig::Write
|
||||
inline wxString wxToString( const Identifier& str ) { return str.GET(); }
|
||||
|
||||
//! Template generates different TaggedIdentifier classes that don't interconvert implicitly
|
||||
/*! The second template parameter determines whether comparisons are case
|
||||
sensitive; default is case sensitive */
|
||||
template<typename Tag, bool CaseSensitive = true >
|
||||
class TaggedIdentifier : public Identifier
|
||||
{
|
||||
public:
|
||||
|
||||
using TagType = Tag;
|
||||
|
||||
using Identifier::Identifier;
|
||||
TaggedIdentifier() = default;
|
||||
|
||||
//! Copy allowed only for the same Tag class and case sensitivity
|
||||
TaggedIdentifier( const TaggedIdentifier& ) = default;
|
||||
//! Move allowed only for the same Tag class and case sensitivity
|
||||
TaggedIdentifier( TaggedIdentifier&& ) = default;
|
||||
//! Copy allowed only for the same Tag class and case sensitivity
|
||||
TaggedIdentifier& operator= ( const TaggedIdentifier& ) = default;
|
||||
//! Move allowed only for the same Tag class and case sensitivity
|
||||
TaggedIdentifier& operator= ( TaggedIdentifier&& ) = default;
|
||||
|
||||
//! Copy prohibited for other Tag classes or case sensitivity
|
||||
template< typename Tag2, bool b >
|
||||
TaggedIdentifier( const TaggedIdentifier<Tag2, b>& ) PROHIBITED;
|
||||
//! Move prohibited for other Tag classes or case sensitivity
|
||||
template< typename Tag2, bool b >
|
||||
TaggedIdentifier( TaggedIdentifier<Tag2, b>&& ) PROHIBITED;
|
||||
//! Copy prohibited for other Tag classes or case sensitivity
|
||||
template< typename Tag2, bool b >
|
||||
TaggedIdentifier& operator= ( const TaggedIdentifier<Tag2, b>& ) PROHIBITED;
|
||||
//! Move prohibited for other Tag classes or case sensitivity
|
||||
template< typename Tag2, bool b >
|
||||
TaggedIdentifier& operator= ( TaggedIdentifier<Tag2, b>&& ) PROHIBITED;
|
||||
|
||||
/*! Allow implicit conversion to this class from un-tagged Identifier,
|
||||
but not from; resolution will use other overloads above if argument has a tag */
|
||||
TaggedIdentifier(const Identifier &str) : Identifier{ str } {}
|
||||
|
||||
//! Explicit conversion to another kind of TaggedIdentifier
|
||||
template<typename String, typename = typename String::TagType>
|
||||
String CONVERT() const
|
||||
{ return String{ this->GET() }; }
|
||||
};
|
||||
|
||||
/*! Comparison of a TaggedIdentifier with an Identifier is allowed, resolving
|
||||
to one of the operators on Identifiers, but always case sensitive.
|
||||
Comparison operators for two TaggedIdentifiers require the same tags and case sensitivity. */
|
||||
template< typename Tag1, typename Tag2, bool b1, bool b2 >
|
||||
inline bool operator == (
|
||||
const TaggedIdentifier< Tag1, b1 > &x, const TaggedIdentifier< Tag2, b2 > &y )
|
||||
{
|
||||
static_assert( std::is_same< Tag1, Tag2 >::value && b1 == b2,
|
||||
"TaggedIdentifiers with different tags or sensitivity are not comparable" );
|
||||
// This test should be eliminated at compile time:
|
||||
if ( b1 )
|
||||
return x.GET(). Cmp ( y.GET() ) == 0;
|
||||
else
|
||||
return x.GET(). CmpNoCase ( y.GET() ) == 0;
|
||||
}
|
||||
|
||||
template< typename Tag1, typename Tag2, bool b1, bool b2 >
|
||||
inline bool operator != (
|
||||
const TaggedIdentifier< Tag1, b1 > &x, const TaggedIdentifier< Tag2, b2 > &y )
|
||||
{ return !(x == y); }
|
||||
|
||||
template< typename Tag1, typename Tag2, bool b1, bool b2 >
|
||||
inline bool operator < (
|
||||
const TaggedIdentifier< Tag1, b1 > &x, const TaggedIdentifier< Tag2, b2 > &y )
|
||||
{
|
||||
static_assert( std::is_same< Tag1, Tag2 >::value && b1 == b2,
|
||||
"TaggedIdentifiers with different tags or sensitivity are not comparable" );
|
||||
// This test should be eliminated at compile time:
|
||||
if ( b1 )
|
||||
return x.GET(). Cmp ( y.GET() ) < 0;
|
||||
else
|
||||
return x.GET(). CmpNoCase ( y.GET() ) < 0;
|
||||
}
|
||||
|
||||
template< typename Tag1, typename Tag2, bool b1, bool b2 >
|
||||
inline bool operator > (
|
||||
const TaggedIdentifier< Tag1, b1 > &x, const TaggedIdentifier< Tag2, b2 > &y )
|
||||
{ return y < x; }
|
||||
|
||||
template< typename Tag1, typename Tag2, bool b1, bool b2 >
|
||||
inline bool operator <= (
|
||||
const TaggedIdentifier< Tag1, b1 > &x, const TaggedIdentifier< Tag2, b2 > &y )
|
||||
{ return !(y < x); }
|
||||
|
||||
template< typename Tag1, typename Tag2, bool b1, bool b2 >
|
||||
inline bool operator >= (
|
||||
const TaggedIdentifier< Tag1, b1 > &x, const TaggedIdentifier< Tag2, b2 > &y )
|
||||
{ return !(x < y); }
|
||||
|
||||
namespace std
|
||||
{
|
||||
template<typename Tag, bool b > struct hash< TaggedIdentifier<Tag, b> >
|
||||
: hash< Identifier > {};
|
||||
}
|
||||
|
||||
/**************************************************************************//**
|
||||
|
||||
\brief type alias for identifying a Plugin supplied by a module, each module
|
||||
defining its own interpretation of the strings, which may or may not be as a
|
||||
file system path
|
||||
********************************************************************************/
|
||||
using PluginPath = wxString;
|
||||
using PluginPaths = std::vector< PluginPath >;
|
||||
|
||||
// A key to be passed to wxConfigBase
|
||||
using RegistryPath = wxString;
|
||||
using RegistryPaths = std::vector< RegistryPath >;
|
||||
|
||||
class wxArrayStringEx;
|
||||
|
||||
//! File extension, not including any leading dot
|
||||
using FileExtension = wxString;
|
||||
using FileExtensions = wxArrayStringEx;
|
||||
|
||||
using FilePath = wxString;
|
||||
using FilePaths = wxArrayStringEx;
|
||||
|
||||
struct CommandIdTag;
|
||||
//! Identifies a menu command or macro. Case-insensitive comparison
|
||||
using CommandID = TaggedIdentifier< CommandIdTag, false >;
|
||||
using CommandIDs = std::vector<CommandID>;
|
||||
|
||||
#endif
|
||||
|
||||
260
libraries/lib-strings/Internat.cpp
Normal file
260
libraries/lib-strings/Internat.cpp
Normal file
@@ -0,0 +1,260 @@
|
||||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
Internat.cpp
|
||||
|
||||
Markus Meyer
|
||||
Dominic Mazzoni (Mac OS X code)
|
||||
|
||||
*******************************************************************//*!
|
||||
|
||||
\class Internat
|
||||
\brief Internationalisation support.
|
||||
|
||||
This class is used to help internationalisation and in general
|
||||
compatibility with different locales and character sets.
|
||||
It deals mostly with converting numbers, but also has important
|
||||
functions to convert to/from UTF-8, which is used in XML files
|
||||
and on Mac OS X for the filesystem.
|
||||
|
||||
*//*******************************************************************/
|
||||
|
||||
#include "Internat.h"
|
||||
|
||||
#include <wx/log.h>
|
||||
#include <wx/intl.h>
|
||||
#include <wx/filename.h>
|
||||
|
||||
#include <locale.h>
|
||||
#include <math.h> // for pow()
|
||||
|
||||
// in order for the static member variables to exist, they must appear here
|
||||
// (_outside_) the class definition, in order to be allocated some storage.
|
||||
// Otherwise, you get link errors.
|
||||
|
||||
wxChar Internat::mDecimalSeparator = wxT('.'); // default
|
||||
// exclude is used by SanitiseFilename.
|
||||
wxArrayString Internat::exclude;
|
||||
|
||||
// DA: Use tweaked translation mechanism to replace 'Audacity' by 'DarkAudacity'.
|
||||
#ifdef EXPERIMENTAL_DA
|
||||
// This function allows us to replace Audacity by DarkAudacity without peppering
|
||||
// the source code with changes. We split out this step, the customisation, as
|
||||
// it is used on its own (without translation) in the wxTS macro.
|
||||
STRINGS_API const wxString& GetCustomSubstitution(const wxString& str2)
|
||||
{
|
||||
// If contains 'DarkAudacity, already converted.
|
||||
if( str2.Contains( "DarkAudacity" ))
|
||||
return str2;
|
||||
// If does not contain 'Audacity', nothing to do.
|
||||
if( !str2.Contains( "Audacity" ))
|
||||
return str2;
|
||||
wxString str3 = str2;
|
||||
str3.Replace( "Audacity", "DarkAudacity" );
|
||||
str3.Replace( " an DarkAudacity", " a DarkAudacity" );
|
||||
// DA also renames sync-lock(ed) as time-lock(ed).
|
||||
str3.Replace( "Sync-Lock", "Time-Lock" );
|
||||
str3.Replace( "Sync-&Lock", "Time-&Lock" );
|
||||
str3.Replace( "Sync Lock", "Time Lock" );
|
||||
return wxTranslations::GetUntranslatedString(str3);
|
||||
}
|
||||
#else
|
||||
STRINGS_API const wxString& GetCustomSubstitution(const wxString& str1)
|
||||
{
|
||||
return str1 ;
|
||||
}
|
||||
#endif
|
||||
|
||||
// In any translated string, we can replace the name 'Audacity' by 'DarkAudacity'
|
||||
// without requiring translators to see extra strings for the two versions.
|
||||
STRINGS_API const wxString& GetCustomTranslation(const wxString& str1)
|
||||
{
|
||||
const wxString& str2 = wxGetTranslation( str1 );
|
||||
return GetCustomSubstitution( str2 );
|
||||
}
|
||||
|
||||
|
||||
void Internat::Init()
|
||||
{
|
||||
// Save decimal point character
|
||||
struct lconv * localeInfo = localeconv();
|
||||
if (localeInfo)
|
||||
mDecimalSeparator = wxString(wxSafeConvertMB2WX(localeInfo->decimal_point)).GetChar(0);
|
||||
|
||||
// wxLogDebug(wxT("Decimal separator set to '%c'"), mDecimalSeparator);
|
||||
|
||||
// Setup list of characters that aren't allowed in file names
|
||||
// Hey! The default wxPATH_NATIVE does not do as it should.
|
||||
#if defined(__WXMAC__)
|
||||
wxPathFormat format = wxPATH_MAC;
|
||||
#elif defined(__WXGTK__)
|
||||
wxPathFormat format = wxPATH_UNIX;
|
||||
#elif defined(__WXMSW__)
|
||||
wxPathFormat format = wxPATH_WIN;
|
||||
#endif
|
||||
|
||||
// This is supposed to return characters not permitted in paths to files
|
||||
// or to directories
|
||||
auto forbid = wxFileName::GetForbiddenChars(format);
|
||||
|
||||
for (auto cc: forbid) {
|
||||
#if defined(__WXGTK__)
|
||||
if (cc == wxT('*') || cc == wxT('?')) {
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
exclude.push_back(wxString{ cc });
|
||||
}
|
||||
|
||||
// The path separators may not be forbidden, so add them
|
||||
//auto separators = wxFileName::GetPathSeparators(format);
|
||||
|
||||
// Bug 1441 exclude all separators from filenames on all platforms.
|
||||
auto separators = wxString("\\/");
|
||||
|
||||
for(auto cc: separators) {
|
||||
if (forbid.Find(cc) == wxNOT_FOUND)
|
||||
exclude.push_back(wxString{ cc });
|
||||
}
|
||||
}
|
||||
|
||||
void Internat::SetCeeNumberFormat()
|
||||
{
|
||||
wxSetlocale( LC_NUMERIC, "C" );
|
||||
mDecimalSeparator = '.';
|
||||
}
|
||||
|
||||
|
||||
wxChar Internat::GetDecimalSeparator()
|
||||
{
|
||||
return mDecimalSeparator;
|
||||
}
|
||||
|
||||
bool Internat::CompatibleToDouble(const wxString& stringToConvert, double* result)
|
||||
{
|
||||
// Regardless of the locale, always respect comma _and_ point
|
||||
wxString s = stringToConvert;
|
||||
s.Replace(wxT(","), wxString(GetDecimalSeparator()));
|
||||
s.Replace(wxT("."), wxString(GetDecimalSeparator()));
|
||||
return s.ToDouble(result);
|
||||
}
|
||||
|
||||
double Internat::CompatibleToDouble(const wxString& stringToConvert)
|
||||
{
|
||||
double result = 0;
|
||||
Internat::CompatibleToDouble(stringToConvert, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
wxString Internat::ToString(double numberToConvert,
|
||||
int digitsAfterDecimalPoint /* = -1 */)
|
||||
{
|
||||
wxString result = ToDisplayString(
|
||||
numberToConvert, digitsAfterDecimalPoint);
|
||||
|
||||
result.Replace(wxString(GetDecimalSeparator()), wxT("."));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
wxString Internat::ToDisplayString(double numberToConvert,
|
||||
int digitsAfterDecimalPoint /* = -1 */)
|
||||
{
|
||||
wxString decSep = wxString(GetDecimalSeparator());
|
||||
wxString result;
|
||||
|
||||
if (digitsAfterDecimalPoint == -1)
|
||||
{
|
||||
result.Printf(wxT("%f"), numberToConvert);
|
||||
|
||||
// Not all libcs respect the decimal separator, so always convert
|
||||
// any dots found to the decimal separator.
|
||||
result.Replace(wxT("."), decSep);
|
||||
|
||||
if (result.Find(decSep) != -1)
|
||||
{
|
||||
// Strip trailing zeros, but leave one, and decimal separator.
|
||||
int pos = result.length() - 1;
|
||||
while ((pos > 1) &&
|
||||
(result.GetChar(pos) == wxT('0')) &&
|
||||
(result.GetChar(pos - 1) != decSep))
|
||||
pos--;
|
||||
// (Previous code removed all of them and decimal separator.)
|
||||
// if (result.GetChar(pos) == decSep)
|
||||
// pos--; // strip point before empty fractional part
|
||||
result = result.Left(pos+1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
wxString format;
|
||||
format.Printf(wxT("%%.%if"), digitsAfterDecimalPoint);
|
||||
result.Printf(format, numberToConvert);
|
||||
|
||||
// Not all libcs respect the decimal separator, so always convert
|
||||
// any dots found to the decimal separator
|
||||
result.Replace(wxT("."), decSep);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
TranslatableString Internat::FormatSize(wxLongLong size)
|
||||
{
|
||||
/* wxLongLong contains no built-in conversion to double */
|
||||
double dSize = size.GetHi() * pow(2.0, 32); // 2 ^ 32
|
||||
dSize += size.GetLo();
|
||||
|
||||
return FormatSize(dSize);
|
||||
}
|
||||
|
||||
TranslatableString Internat::FormatSize(double size)
|
||||
{
|
||||
TranslatableString sizeStr;
|
||||
|
||||
if (size == -1)
|
||||
sizeStr = XO("Unable to determine");
|
||||
else {
|
||||
/* make it look nice, by formatting into k, MB, etc */
|
||||
if (size < 1024.0)
|
||||
sizeStr = XO("%s bytes").Format( ToDisplayString(size) );
|
||||
else if (size < 1024.0 * 1024.0) {
|
||||
/* i18n-hint: Abbreviation for Kilo bytes */
|
||||
sizeStr = XO("%s KB").Format( ToDisplayString(size / 1024.0, 1) );
|
||||
}
|
||||
else if (size < 1024.0 * 1024.0 * 1024.0) {
|
||||
/* i18n-hint: Abbreviation for Mega bytes */
|
||||
sizeStr = XO("%s MB").Format( ToDisplayString(size / (1024.0 * 1024.0), 1) );
|
||||
}
|
||||
else {
|
||||
/* i18n-hint: Abbreviation for Giga bytes */
|
||||
sizeStr = XO("%s GB").Format( ToDisplayString(size / (1024.0 * 1024.0 * 1024.0), 1) );
|
||||
}
|
||||
}
|
||||
|
||||
return sizeStr;
|
||||
}
|
||||
|
||||
bool Internat::SanitiseFilename(wxString &name, const wxString &sub)
|
||||
{
|
||||
bool result = false;
|
||||
for(const auto &item : exclude)
|
||||
{
|
||||
if(name.Contains(item))
|
||||
{
|
||||
name.Replace(item, sub);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __WXMAC__
|
||||
// Special Mac stuff
|
||||
// '/' is permitted in file names as seen in dialogs, even though it is
|
||||
// the path separator. The "real" filename as seen in the terminal has ':'.
|
||||
// Do NOT return true if this is the only substitution.
|
||||
name.Replace(wxT("/"), wxT(":"));
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
167
libraries/lib-strings/Internat.h
Normal file
167
libraries/lib-strings/Internat.h
Normal file
@@ -0,0 +1,167 @@
|
||||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
Internat.h
|
||||
|
||||
Markus Meyer
|
||||
Dominic Mazzoni (Mac OS X code)
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef __AUDACITY_INTERNAT__
|
||||
#define __AUDACITY_INTERNAT__
|
||||
|
||||
#include <vector>
|
||||
#include <wx/longlong.h>
|
||||
|
||||
#include "TranslatableString.h"
|
||||
|
||||
class wxArrayString;
|
||||
class wxArrayStringEx;
|
||||
|
||||
extern STRINGS_API const wxString& GetCustomTranslation(const wxString& str1 );
|
||||
extern STRINGS_API const wxString& GetCustomSubstitution(const wxString& str1 );
|
||||
|
||||
// Marks string for substitution only.
|
||||
#define _TS( s ) GetCustomSubstitution( s )
|
||||
|
||||
// Marks strings for extraction only... use .Translate() to translate.
|
||||
// '&', preceding menu accelerators, should NOT occur in the argument.
|
||||
#define XO(s) (TranslatableString{ wxT(s), {} })
|
||||
|
||||
// Alternative taking a second context argument. A context is a string literal,
|
||||
// which is not translated, but serves to disambiguate uses of the first string
|
||||
// that might need differing translations, such as "Light" meaning not-heavy in
|
||||
// one place but not-dark elsewhere.
|
||||
#define XC(s, c) (TranslatableString{ wxT(s), {} }.Context(c))
|
||||
|
||||
// Marks strings for extraction only, where '&', preceding menu accelerators, MAY
|
||||
// occur.
|
||||
// For now, expands exactly as macro XO does, but in future there will be a
|
||||
// type distinction - for example XXO should be used for menu item names that
|
||||
// might use the & character for shortcuts.
|
||||
#define XXO(s) XO(s)
|
||||
|
||||
// Corresponds to XC as XXO does to XO
|
||||
#define XXC(s, c) XC(s, c)
|
||||
|
||||
#ifdef _
|
||||
#undef _
|
||||
#endif
|
||||
|
||||
#if defined( _DEBUG )
|
||||
// Force a crash if you misuse _ in a static initializer, so that translation
|
||||
// is looked up too early and not found.
|
||||
|
||||
#ifdef __WXMSW__
|
||||
|
||||
// Eventually pulls in <windows.h> which indirectly defines DebugBreak(). Can't
|
||||
// include <windows.h> directly since it then causes "MemoryX.h" to spew errors.
|
||||
#include <wx/app.h>
|
||||
#define _(s) ((wxTranslations::Get() || (DebugBreak(), true)), \
|
||||
GetCustomTranslation((s)))
|
||||
|
||||
#else
|
||||
|
||||
#include <signal.h>
|
||||
// Raise a signal because it's even too early to use wxASSERT for this.
|
||||
#define _(s) ((wxTranslations::Get() || raise(SIGTRAP)), \
|
||||
GetCustomTranslation((s)))
|
||||
|
||||
#endif
|
||||
|
||||
#else
|
||||
#define _(s) GetCustomTranslation((s))
|
||||
#endif
|
||||
|
||||
#ifdef XP
|
||||
#undef XP
|
||||
#endif
|
||||
|
||||
|
||||
// The two string arguments will go to the .pot file, as
|
||||
// msgid sing
|
||||
// msgid_plural plural
|
||||
//
|
||||
// (You must use plain string literals. Do not use _() or wxT() or L prefix,
|
||||
// which (intentionally) will fail to compile. The macro inserts wxT).
|
||||
//
|
||||
// Note too: The i18n-hint comment must be on the line preceding the first
|
||||
// string. That might be inside the parentheses of the macro call.
|
||||
//
|
||||
// The macro call is then followed by a sequence of format arguments in
|
||||
// parentheses. The third argument of the macro call is the zero-based index
|
||||
// of the format argument that selects singular or plural
|
||||
#define XP(sing, plur, n) \
|
||||
TranslatableString{ wxT(sing), {} }.Plural<(n)>( wxT(plur) )
|
||||
|
||||
// Like XP but with an additional context argument, as for XC
|
||||
#define XPC(sing, plur, n, c) \
|
||||
TranslatableString{ wxT(sing), {} }.Context(c).Plural<(n)>( wxT(plur) )
|
||||
|
||||
class STRINGS_API Internat
|
||||
{
|
||||
public:
|
||||
/** \brief Initialize internationalisation support. Call this once at
|
||||
* program start. */
|
||||
static void Init();
|
||||
|
||||
/** \brief Get the decimal separator for the current locale.
|
||||
*
|
||||
* Normally, this is a decimal point ('.'), but e.g. Germany uses a
|
||||
* comma (',').*/
|
||||
static wxChar GetDecimalSeparator();
|
||||
static void SetCeeNumberFormat();
|
||||
|
||||
/** \brief Convert a string to a number.
|
||||
*
|
||||
* This function will accept BOTH point and comma as a decimal separator,
|
||||
* regardless of the current locale.
|
||||
* Returns 'true' on success, and 'false' if an error occurs. */
|
||||
static bool CompatibleToDouble(const wxString& stringToConvert, double* result);
|
||||
|
||||
// Function version of above.
|
||||
static double CompatibleToDouble(const wxString& stringToConvert);
|
||||
|
||||
/** \brief Convert a number to a string, always uses the dot as decimal
|
||||
* separator*/
|
||||
static wxString ToString(double numberToConvert,
|
||||
int digitsAfterDecimalPoint = -1);
|
||||
|
||||
/** \brief Convert a number to a string, uses the user's locale's decimal
|
||||
* separator */
|
||||
static wxString ToDisplayString(double numberToConvert,
|
||||
int digitsAfterDecimalPoint = -1);
|
||||
|
||||
/** \brief Convert a number to a string while formatting it in bytes, KB,
|
||||
* MB, GB */
|
||||
static TranslatableString FormatSize(wxLongLong size);
|
||||
static TranslatableString FormatSize(double size);
|
||||
|
||||
/** \brief Check a proposed file name string for illegal characters and
|
||||
* remove them
|
||||
* return true iff name is "visibly" changed (not necessarily equivalent to
|
||||
* character-wise changed)
|
||||
*/
|
||||
static bool SanitiseFilename(wxString &name, const wxString &sub);
|
||||
|
||||
static const wxArrayString &GetExcludedCharacters()
|
||||
{ return exclude; }
|
||||
|
||||
private:
|
||||
static wxChar mDecimalSeparator;
|
||||
|
||||
static wxArrayString exclude;
|
||||
};
|
||||
|
||||
// Convert C strings to wxString
|
||||
#define UTF8CTOWX(X) wxString((X), wxConvUTF8)
|
||||
#define LAT1CTOWX(X) wxString((X), wxConvISO8859_1)
|
||||
|
||||
// Whether disambiguationg contexts are supported
|
||||
// If not, then the program builds and runs, but strings in the catalog with
|
||||
// contexts will fail to translate
|
||||
#define HAS_I18N_CONTEXTS wxCHECK_VERSION(3, 1, 1)
|
||||
|
||||
#endif
|
||||
409
libraries/lib-strings/Languages.cpp
Normal file
409
libraries/lib-strings/Languages.cpp
Normal file
@@ -0,0 +1,409 @@
|
||||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
Languages.cpp
|
||||
|
||||
Dominic Mazzoni
|
||||
|
||||
|
||||
*******************************************************************//*!
|
||||
|
||||
\file Languages.cpp
|
||||
\brief Determine installed languages.
|
||||
|
||||
Figure out what translations are installed and return a list
|
||||
of language codes (like "es", "fr", or "pt-br") and corresponding
|
||||
language names (like "Español", "Français", and "Português").
|
||||
We use our own list of translations of language names (i.e.
|
||||
"Français" instead of "French") but we fallback on the language
|
||||
name in wxWidgets if we don't have it listed.
|
||||
|
||||
This code is designed to work well with all of the current
|
||||
languages, but adapt to any language that wxWidgets supports.
|
||||
Other languages will only be supported if they're added to
|
||||
the database using wxLocale::AddLanguage.
|
||||
|
||||
But for the most part, this means that somebody could add a NEW
|
||||
translation and have it work immediately.
|
||||
|
||||
*//*******************************************************************/
|
||||
|
||||
|
||||
|
||||
#include "Languages.h"
|
||||
#include <memory>
|
||||
#include "wxArrayStringEx.h"
|
||||
|
||||
#include "Internat.h"
|
||||
#include "wxArrayStringEx.h"
|
||||
|
||||
#include <wx/defs.h>
|
||||
#include <wx/dir.h>
|
||||
#include <wx/filename.h>
|
||||
#include <wx/intl.h>
|
||||
#include <wx/textfile.h>
|
||||
#include <wx/utils.h> // for wxSetEnv
|
||||
|
||||
#include <clocale>
|
||||
#include <unordered_map>
|
||||
|
||||
using LangHash = std::unordered_map<wxString, TranslatableString>;
|
||||
using ReverseLangHash = std::unordered_map<TranslatableString, wxString>;
|
||||
|
||||
static void FindFilesInPathList(const wxString & pattern,
|
||||
const FilePaths & pathList, FilePaths & results)
|
||||
{
|
||||
wxFileName ff;
|
||||
for (const auto &path : pathList) {
|
||||
ff = path + wxFILE_SEP_PATH + pattern;
|
||||
wxDir::GetAllFiles(ff.GetPath(), &results, ff.GetFullName(), wxDIR_FILES);
|
||||
}
|
||||
}
|
||||
|
||||
static bool TranslationExists(const FilePaths &pathList, wxString code)
|
||||
{
|
||||
FilePaths results;
|
||||
FindFilesInPathList(code + L"/audacity.mo", pathList, results);
|
||||
#if defined(__WXMAC__)
|
||||
FindFilesInPathList(code + L".lproj/audacity.mo", pathList, results);
|
||||
#endif
|
||||
FindFilesInPathList(code + L"/LC_MESSAGES/audacity.mo", pathList, results);
|
||||
return (results.size() > 0);
|
||||
}
|
||||
|
||||
#ifdef __WXMAC__
|
||||
#include <CoreFoundation/CFLocale.h>
|
||||
#include <wx/osx/core/cfstring.h>
|
||||
#endif
|
||||
|
||||
namespace Languages {
|
||||
|
||||
wxString GetSystemLanguageCode(const FilePaths &pathList)
|
||||
{
|
||||
wxArrayString langCodes;
|
||||
TranslatableStrings langNames;
|
||||
|
||||
GetLanguages(pathList, langCodes, langNames);
|
||||
|
||||
int sysLang = wxLocale::GetSystemLanguage();
|
||||
|
||||
const wxLanguageInfo *info;
|
||||
|
||||
#ifdef __WXMAC__
|
||||
// PRL: Bug 1227, system language on Mac may not be right because wxW3 is
|
||||
// dependent on country code too in wxLocale::GetSystemLanguage().
|
||||
|
||||
if (sysLang == wxLANGUAGE_UNKNOWN)
|
||||
{
|
||||
// wxW3 did a too-specific lookup of language and country, when
|
||||
// there is nothing for that combination; try it by language alone.
|
||||
|
||||
// The following lines are cribbed from that function.
|
||||
wxCFRef<CFLocaleRef> userLocaleRef(CFLocaleCopyCurrent());
|
||||
wxCFStringRef str(wxCFRetain((CFStringRef)CFLocaleGetValue(userLocaleRef, kCFLocaleLanguageCode)));
|
||||
auto lang = str.AsString();
|
||||
|
||||
// Now avoid wxLocale::GetLanguageInfo(), instead calling:
|
||||
info = wxLocale::FindLanguageInfo(lang);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
info = wxLocale::GetLanguageInfo(sysLang);
|
||||
}
|
||||
|
||||
if (info) {
|
||||
wxString fullCode = info->CanonicalName;
|
||||
if (fullCode.length() < 2)
|
||||
return wxT("en");
|
||||
|
||||
wxString code = fullCode.Left(2);
|
||||
unsigned int i;
|
||||
|
||||
for(i=0; i<langCodes.size(); i++) {
|
||||
if (langCodes[i] == fullCode)
|
||||
return fullCode;
|
||||
|
||||
if (langCodes[i] == code)
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
return wxT("en");
|
||||
}
|
||||
|
||||
void GetLanguages( FilePaths pathList,
|
||||
wxArrayString &langCodes, TranslatableStrings &langNames)
|
||||
{
|
||||
static const char *const utf8Names[] = {
|
||||
"af Afrikaans",
|
||||
"ar \330\247\331\204\330\271\330\261\330\250\331\212\330\251",
|
||||
"be \320\221\320\265\320\273\320\260\321\200\321\203\321\201\320\272\320\260\321\217",
|
||||
"bg \320\221\321\212\320\273\320\263\320\260\321\200\321\201\320\272\320\270",
|
||||
"bn \340\246\254\340\246\276\340\246\202\340\246\262\340\246\276",
|
||||
"bs Bosanski",
|
||||
"ca Catal\303\240",
|
||||
"ca_ES@valencia Valenci\303\240",
|
||||
"co Corsu",
|
||||
"cs \304\214e\305\241tina",
|
||||
"cy Cymraeg",
|
||||
"da Dansk",
|
||||
"de Deutsch",
|
||||
"el \316\225\316\273\316\273\316\267\316\275\316\271\316\272\316\254",
|
||||
"en English",
|
||||
"es Espa\303\261ol",
|
||||
"eu Euskara",
|
||||
"eu_ES Euskara (Espainiako)",
|
||||
"fa \331\201\330\247\330\261\330\263\333\214",
|
||||
"fi Suomi",
|
||||
"fr Fran\303\247ais",
|
||||
"ga Gaeilge",
|
||||
"gl Galego",
|
||||
"he \327\242\327\221\327\250\327\231\327\252",
|
||||
"hi \340\244\271\340\244\277\340\244\250\340\245\215\340\244\246\340\245\200",
|
||||
"hr Hrvatski",
|
||||
"hu Magyar",
|
||||
"hy \325\200\325\241\325\265\325\245\326\200\325\245\325\266",
|
||||
"id Bahasa Indonesia",
|
||||
"it Italiano",
|
||||
"ja \346\227\245\346\234\254\350\252\236",
|
||||
"ka \341\203\245\341\203\220\341\203\240\341\203\227\341\203\243\341\203\232\341\203\230",
|
||||
"km \341\236\201\341\237\201\341\236\230\341\236\232\341\236\227\341\236\266\341\236\237\341\236\266",
|
||||
"ko \355\225\234\352\265\255\354\226\264",
|
||||
"lt Lietuvi\305\263",
|
||||
"mk \320\234\320\260\320\272\320\265\320\264\320\276\320\275\321\201\320\272\320\270",
|
||||
"mr \340\244\256\340\244\260\340\244\276\340\244\240\340\245\200",
|
||||
"my \341\200\231\341\200\274\341\200\224\341\200\272\341\200\231\341\200\254\341\200\205\341\200\254",
|
||||
"nb Norsk",
|
||||
"nl Nederlands",
|
||||
"oc Occitan",
|
||||
"pl Polski",
|
||||
"pt Portugu\303\252s",
|
||||
"pt_BR Portugu\303\252s (Brasil)",
|
||||
"ro Rom\303\242n\304\203",
|
||||
"ru \320\240\321\203\321\201\321\201\320\272\320\270\320\271",
|
||||
"sk Sloven\304\215ina",
|
||||
"sl Sloven\305\241\304\215ina",
|
||||
"sr_RS \320\241\321\200\320\277\321\201\320\272\320\270",
|
||||
"sr_RS@latin Srpski",
|
||||
"sv Svenska",
|
||||
"ta \340\256\244\340\256\256\340\256\277\340\256\264\340\257\215",
|
||||
"tg \320\242\320\276\322\267\320\270\320\272\323\243",
|
||||
"tr T\303\274rk\303\247e",
|
||||
"uk \320\243\320\272\321\200\320\260\321\227\320\275\321\201\321\214\320\272\320\260",
|
||||
"vi Ti\341\272\277ng Vi\341\273\207t",
|
||||
"zh_CN \344\270\255\346\226\207\357\274\210\347\256\200\344\275\223\357\274\211",
|
||||
"zh_TW \344\270\255\346\226\207\357\274\210\347\271\201\351\253\224\357\274\211",
|
||||
};
|
||||
|
||||
TranslatableStrings tempNames;
|
||||
wxArrayString tempCodes;
|
||||
ReverseLangHash reverseHash;
|
||||
LangHash tempHash;
|
||||
|
||||
const LangHash localLanguageName = []{
|
||||
LangHash localLanguageName;
|
||||
for ( auto utf8Name : utf8Names )
|
||||
{
|
||||
auto str = wxString::FromUTF8(utf8Name);
|
||||
auto code = str.BeforeFirst(' ');
|
||||
auto name = str.AfterFirst(' ');
|
||||
localLanguageName[code] = Verbatim( name );
|
||||
}
|
||||
return localLanguageName;
|
||||
}();
|
||||
|
||||
#if defined(__WXGTK__)
|
||||
{
|
||||
wxFileName pathNorm{ wxString{INSTALL_PREFIX} + L"/share/locale" };
|
||||
pathNorm.Normalize();
|
||||
const wxString newPath{ pathNorm.GetFullPath() };
|
||||
if (pathList.end() ==
|
||||
std::find(pathList.begin(), pathList.end(), newPath))
|
||||
pathList.push_back(newPath);
|
||||
}
|
||||
#endif
|
||||
|
||||
// For each language in our list we look for a corresponding entry in
|
||||
// wxLocale.
|
||||
for ( auto end = localLanguageName.end(), i = localLanguageName.begin();
|
||||
i != end; ++i )
|
||||
{
|
||||
const wxLanguageInfo *info = wxLocale::FindLanguageInfo(i->first);
|
||||
|
||||
if (!info) {
|
||||
wxASSERT(info != NULL);
|
||||
continue;
|
||||
}
|
||||
|
||||
wxString fullCode = info->CanonicalName;
|
||||
wxString code = fullCode.Left(2);
|
||||
auto name = Verbatim( info->Description );
|
||||
|
||||
// Logic: Languages codes are sometimes hierarchical, with a
|
||||
// general language code and then a subheading. For example,
|
||||
// zh_TW for Traditional Chinese and zh_CN for Simplified
|
||||
// Chinese - but just zh for Chinese in general. First we look
|
||||
// for the full code, like zh_TW. If that doesn't exist, we
|
||||
// look for a code corresponding to the first two letters.
|
||||
// Note that if the language for a fullCode exists but we only
|
||||
// have a name for the short code, we will use the short code's
|
||||
// name but associate it with the full code. This allows someone
|
||||
// to drop in a NEW language and still get reasonable behavior.
|
||||
|
||||
if (fullCode.length() < 2)
|
||||
continue;
|
||||
|
||||
auto found = localLanguageName.find( code );
|
||||
if ( found != end ) {
|
||||
name = found->second;
|
||||
}
|
||||
found = localLanguageName.find( fullCode );
|
||||
if ( found != end ) {
|
||||
name = found->second;
|
||||
}
|
||||
|
||||
if (TranslationExists(pathList, fullCode)) {
|
||||
code = fullCode;
|
||||
}
|
||||
|
||||
if (!tempHash[code].empty())
|
||||
continue;
|
||||
|
||||
if (TranslationExists(pathList, code) || code==wxT("en")) {
|
||||
tempCodes.push_back(code);
|
||||
tempNames.push_back(name);
|
||||
tempHash[code] = name;
|
||||
|
||||
/* wxLogDebug(wxT("code=%s name=%s fullCode=%s name=%s -> %s"),
|
||||
code, localLanguageName[code],
|
||||
fullCode, localLanguageName[fullCode],
|
||||
name);*/
|
||||
}
|
||||
}
|
||||
|
||||
// JKC: Adding language for simplified audacity.
|
||||
{
|
||||
wxString code;
|
||||
code = wxT("en-simple");
|
||||
auto name = XO("Simplified");
|
||||
if (TranslationExists(pathList, code) ) {
|
||||
tempCodes.push_back(code);
|
||||
tempNames.push_back(name);
|
||||
tempHash[code] = name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Sort
|
||||
unsigned int j;
|
||||
for(j=0; j<tempNames.size(); j++){
|
||||
reverseHash[tempNames[j]] = tempCodes[j];
|
||||
}
|
||||
|
||||
std::sort( tempNames.begin(), tempNames.end(),
|
||||
[]( const TranslatableString &a, const TranslatableString &b ){
|
||||
return a.Translation() < b.Translation();
|
||||
} );
|
||||
|
||||
// Add system language
|
||||
langNames.push_back(XO("System"));
|
||||
langCodes.push_back(wxT("System"));
|
||||
|
||||
for(j=0; j<tempNames.size(); j++) {
|
||||
langNames.push_back(tempNames[j]);
|
||||
langCodes.push_back(reverseHash[tempNames[j]]);
|
||||
}
|
||||
}
|
||||
|
||||
static std::unique_ptr<wxLocale> sLocale;
|
||||
static wxString sLocaleName;
|
||||
|
||||
wxString SetLang( const FilePaths &pathList, const wxString & lang )
|
||||
{
|
||||
wxString result = lang;
|
||||
|
||||
sLocale.reset();
|
||||
|
||||
#if defined(__WXMAC__)
|
||||
// This should be reviewed again during the wx3 conversion.
|
||||
|
||||
// On OSX, if the LANG environment variable isn't set when
|
||||
// using a language like Japanese, an assertion will trigger
|
||||
// because conversion to Japanese from "?" doesn't return a
|
||||
// valid length, so make OSX happy by defining/overriding
|
||||
// the LANG environment variable with U.S. English for now.
|
||||
wxSetEnv(wxT("LANG"), wxT("en_US.UTF-8"));
|
||||
#endif
|
||||
|
||||
const wxLanguageInfo *info = NULL;
|
||||
if (!lang.empty() && lang != wxT("System")) {
|
||||
// Try to find the given language
|
||||
info = wxLocale::FindLanguageInfo(lang);
|
||||
}
|
||||
if (!info)
|
||||
{
|
||||
// Not given a language or can't find it; substitute the system language
|
||||
result = Languages::GetSystemLanguageCode(pathList);
|
||||
info = wxLocale::FindLanguageInfo(result);
|
||||
if (!info)
|
||||
// Return the substituted system language, but we can't complete setup
|
||||
// Should we try to do something better?
|
||||
return result;
|
||||
}
|
||||
sLocale = std::make_unique<wxLocale>(info->Language);
|
||||
|
||||
for( const auto &path : pathList )
|
||||
sLocale->AddCatalogLookupPathPrefix( path );
|
||||
|
||||
// LL: Must add the wxWidgets catalog manually since the search
|
||||
// paths were not set up when mLocale was created. The
|
||||
// catalogs are search in LIFO order, so add wxstd first.
|
||||
sLocale->AddCatalog(wxT("wxstd"));
|
||||
|
||||
// Must match TranslationExists() in Languages.cpp
|
||||
sLocale->AddCatalog("audacity");
|
||||
|
||||
// Initialize internationalisation (number formats etc.)
|
||||
//
|
||||
// This must go _after_ creating the wxLocale instance because
|
||||
// creating the wxLocale instance sets the application-wide locale.
|
||||
|
||||
Internat::Init();
|
||||
|
||||
using future1 = decltype(
|
||||
// The file of unused strings is part of the source tree scanned by
|
||||
// xgettext when compiling the catalog template audacity.pot.
|
||||
// Including it here doesn't change that but does make the C++ compiler
|
||||
// check for correct syntax, but also generate no object code for them.
|
||||
#include "UnusedStrings.h"
|
||||
0
|
||||
);
|
||||
|
||||
sLocaleName = wxSetlocale(LC_ALL, NULL);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
wxString GetLocaleName()
|
||||
{
|
||||
return sLocaleName;
|
||||
}
|
||||
|
||||
wxString GetLang()
|
||||
{
|
||||
if (sLocale)
|
||||
return sLocale->GetSysName();
|
||||
else
|
||||
return {};
|
||||
}
|
||||
|
||||
wxString GetLangShort()
|
||||
{
|
||||
if (sLocale)
|
||||
return sLocale->GetName();
|
||||
else
|
||||
return {};
|
||||
}
|
||||
}
|
||||
60
libraries/lib-strings/Languages.h
Normal file
60
libraries/lib-strings/Languages.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
Languages.h
|
||||
|
||||
Dominic Mazzoni
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef __AUDACITY_LANGUAGES__
|
||||
#define __AUDACITY_LANGUAGES__
|
||||
|
||||
class wxArrayString;
|
||||
class wxString;
|
||||
|
||||
#include "Identifier.h"
|
||||
#include "Internat.h"
|
||||
|
||||
namespace Languages {
|
||||
|
||||
/*!
|
||||
@param pathList paths to search for .mo files, grouped into subdirectories for the different
|
||||
languages
|
||||
@param[out] langCodes two-letter language abbreviations (like "fr") or language and country
|
||||
(like "pt_BR")
|
||||
@param[out] langNames corresponding autonyms of those languages (like "Português")
|
||||
*/
|
||||
STRINGS_API
|
||||
void GetLanguages( FilePaths pathList,
|
||||
wxArrayString &langCodes, TranslatableStrings &langNames);
|
||||
|
||||
/*!
|
||||
@param pathList paths to search for .mo files, grouped into subdirectories for the different languages
|
||||
*/
|
||||
STRINGS_API
|
||||
wxString GetSystemLanguageCode(const FilePaths &pathList);
|
||||
|
||||
/*!
|
||||
@param audacityPathList paths to search for .mo files, grouped into subdirectories for the different languages
|
||||
@param lang a language code; or if empty or "System", then default to system language.
|
||||
@return the language code actually used which is not lang if lang cannot be found. */
|
||||
STRINGS_API
|
||||
wxString SetLang( const FilePaths &audacityPathList, const wxString & lang );
|
||||
|
||||
/*! @return the last language code that was set */
|
||||
STRINGS_API
|
||||
wxString GetLang();
|
||||
|
||||
/*! @return the last language code that was set (minus country code) */
|
||||
STRINGS_API
|
||||
wxString GetLangShort();
|
||||
|
||||
/*! @return a string as from setlocale() */
|
||||
STRINGS_API
|
||||
wxString GetLocaleName();
|
||||
|
||||
}
|
||||
|
||||
#endif // __AUDACITY_LANGUAGES__
|
||||
152
libraries/lib-strings/TranslatableString.cpp
Normal file
152
libraries/lib-strings/TranslatableString.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
@file TranslatableString.cpp
|
||||
|
||||
Paul Licameli split from Internat.cpp
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include "TranslatableString.h"
|
||||
#include "Identifier.h"
|
||||
#include <wx/translation.h>
|
||||
|
||||
const wxChar *const TranslatableString::NullContextName = wxT("*");
|
||||
|
||||
Identifier TranslatableString::MSGID() const
|
||||
{
|
||||
return Identifier{ mMsgid };
|
||||
}
|
||||
|
||||
const TranslatableString::Formatter
|
||||
TranslatableString::NullContextFormatter {
|
||||
[](const wxString & str, TranslatableString::Request request) -> wxString {
|
||||
switch ( request ) {
|
||||
case Request::Context:
|
||||
return NullContextName;
|
||||
case Request::Format:
|
||||
case Request::DebugFormat:
|
||||
default:
|
||||
return str;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bool TranslatableString::IsVerbatim() const
|
||||
{
|
||||
return DoGetContext( mFormatter ) == NullContextName;
|
||||
}
|
||||
|
||||
TranslatableString &TranslatableString::Strip( unsigned codes ) &
|
||||
{
|
||||
auto prevFormatter = mFormatter;
|
||||
mFormatter = [prevFormatter, codes]
|
||||
( const wxString & str, TranslatableString::Request request ) -> wxString {
|
||||
switch ( request ) {
|
||||
case Request::Context:
|
||||
return TranslatableString::DoGetContext( prevFormatter );
|
||||
case Request::Format:
|
||||
case Request::DebugFormat:
|
||||
default: {
|
||||
bool debug = request == Request::DebugFormat;
|
||||
auto result =
|
||||
TranslatableString::DoSubstitute(
|
||||
prevFormatter,
|
||||
str, TranslatableString::DoGetContext( prevFormatter ),
|
||||
debug );
|
||||
if ( codes & MenuCodes ) {
|
||||
// Don't use this, it's in wxCore
|
||||
// result = wxStripMenuCodes( result );
|
||||
decltype( result ) temp;
|
||||
temp.swap(result);
|
||||
for ( auto iter = temp.begin(), end = temp.end();
|
||||
iter != end; ++iter ) {
|
||||
// Stop at trailing hot key name
|
||||
if ( *iter == '\t' )
|
||||
break;
|
||||
// Strip & (unless escaped by another preceding &)
|
||||
if ( *iter == '&' && ++iter == end )
|
||||
break;
|
||||
result.append( 1, *iter );
|
||||
}
|
||||
}
|
||||
if ( codes & Ellipses ) {
|
||||
if (result.EndsWith(wxT("...")))
|
||||
result = result.Left( result.length() - 3 );
|
||||
// Also check for the single-character Unicode ellipsis
|
||||
else if (result.EndsWith(wxT("\u2026")))
|
||||
result = result.Left( result.length() - 1 );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
wxString TranslatableString::DoGetContext( const Formatter &formatter )
|
||||
{
|
||||
return formatter ? formatter( {}, Request::Context ) : wxString{};
|
||||
}
|
||||
|
||||
wxString TranslatableString::DoSubstitute( const Formatter &formatter,
|
||||
const wxString &format, const wxString &context, bool debug )
|
||||
{
|
||||
return formatter
|
||||
? formatter( format, debug ? Request::DebugFormat : Request::Format )
|
||||
: // come here for most translatable strings, which have no formatting
|
||||
( debug ? format : wxGetTranslation( format, wxString{}, context ) );
|
||||
}
|
||||
|
||||
wxString TranslatableString::DoChooseFormat(
|
||||
const Formatter &formatter,
|
||||
const wxString &singular, const wxString &plural, unsigned nn, bool debug )
|
||||
{
|
||||
// come here for translatable strings that choose among forms by number;
|
||||
// if not debugging, then two keys are passed to an overload of
|
||||
// wxGetTranslation, and also a number.
|
||||
// Some languages might choose among more or fewer than two forms
|
||||
// (e.g. Arabic has duals and Russian has complicated declension rules)
|
||||
wxString context;
|
||||
return ( debug || NullContextName == (context = DoGetContext(formatter)) )
|
||||
? ( nn == 1 ? singular : plural )
|
||||
: wxGetTranslation(
|
||||
singular, plural, nn
|
||||
#if HAS_I18N_CONTEXTS
|
||||
, wxString{} // domain
|
||||
, context
|
||||
#endif
|
||||
);
|
||||
}
|
||||
|
||||
TranslatableString &TranslatableString::Join(
|
||||
const TranslatableString arg, const wxString &separator ) &
|
||||
{
|
||||
auto prevFormatter = mFormatter;
|
||||
mFormatter =
|
||||
[prevFormatter,
|
||||
arg /* = std::move( arg ) */,
|
||||
separator](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
|
||||
TranslatableString::DoSubstitute( prevFormatter,
|
||||
str, TranslatableString::DoGetContext( prevFormatter ),
|
||||
debug )
|
||||
+ separator
|
||||
+ arg.DoFormat( debug );
|
||||
}
|
||||
}
|
||||
};
|
||||
return *this;
|
||||
}
|
||||
|
||||
const TranslatableString TranslatableString::Inaudible{ wxT("\a") };
|
||||
324
libraries/lib-strings/TranslatableString.h
Normal file
324
libraries/lib-strings/TranslatableString.h
Normal file
@@ -0,0 +1,324 @@
|
||||
/**********************************************************************
|
||||
|
||||
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 ) ); }
|
||||
|
||||
#endif
|
||||
80
libraries/lib-strings/UnusedStrings.h
Normal file
80
libraries/lib-strings/UnusedStrings.h
Normal file
@@ -0,0 +1,80 @@
|
||||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
@file UnusedStrings.h
|
||||
|
||||
Paul Licameli
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
/*
|
||||
This file contains strings and comments put into the source code tree for the
|
||||
string freeze of a release, but while not yet in feature freeze. This allows
|
||||
the translation catalog template, locale/audacity.pot, to stabilize long enough
|
||||
in advance of the release, that translators have enough opportunity to update
|
||||
the language-specific catalogs.
|
||||
|
||||
These strings, and their i18n-hint comments, can be cut and pasted elsewhere
|
||||
during the continued development in string freeze.
|
||||
|
||||
Some example strings are also given first, to document the syntax.
|
||||
*/
|
||||
|
||||
// //////////////////////////////////////////////// Begin examples
|
||||
|
||||
// A translated string may also have an accompanying special comment
|
||||
|
||||
// i18n-hint to lower oneself
|
||||
XO("Get down"),
|
||||
|
||||
/* i18n-hint An example of a longer special comment that can wrap lines.
|
||||
These comments must begin with the special word i18n-hint and immediately
|
||||
precede the source code line with the opening quotation mark. The translators
|
||||
will see this comment text in the generated .po files, which they edit and
|
||||
complete. This comment can give hints about unusual words, or the grammatical
|
||||
form of an English word (such as whether it's meant as noun or verb) when that
|
||||
is not clear from context.
|
||||
*/
|
||||
XO("Get down"),
|
||||
|
||||
XO(
|
||||
/* i18n-hint one more example of alternative correct placement of an
|
||||
internationalization comment. All comments on different occurrences of the
|
||||
same string are collected in the template file. */
|
||||
"Get down"),
|
||||
|
||||
// Next is an example of a string with a disambiguating context. This allows
|
||||
// one English string to have more than one entry in the translation catalog.
|
||||
// The context string is not itself seen by users or translated by translators.
|
||||
// It only allows the code at runtime to choose the correct translation.
|
||||
|
||||
// i18n-hint Have fun and dance
|
||||
XC("Get down", "party"),
|
||||
|
||||
// i18n-hint Obtain the underfeathers of a goose
|
||||
XC("Get down", "pillows"),
|
||||
|
||||
// Strings may have blanks in which names are substituted, such as a file name.
|
||||
// For good internationalization, allowing for variations of grammar between
|
||||
// languages, and giving translators the needed context, we substitute names
|
||||
// into larger phrases. Do not build up messages by concatenating less complete
|
||||
// phrases.
|
||||
XO("Your batch command of %s was not recognized."),
|
||||
|
||||
// Sometimes strings must be given as singular-plural pairs. The translation
|
||||
// system may make more or fewer than two entries in the catalog, depending
|
||||
// on the grammatical rules of the language. The 0 is a placeholder for an
|
||||
// expression in real code that is used to choose the proper string at
|
||||
// lookup time.
|
||||
// Also %d is a blank appropriate for a number instead of a name.
|
||||
|
||||
// i18n-hint
|
||||
XP("Got down one time", "Got down %d times", 0),
|
||||
|
||||
// Finally, singular-plural pairs may also have context strings.
|
||||
|
||||
// i18n-hint Obtained underfeathers of geese on one or more occasions
|
||||
XPC("Got down one time", "Got down %d times", 0, "pillows"),
|
||||
|
||||
// //////////////////////////////////////////////// End examples
|
||||
13
libraries/lib-strings/wxArrayStringEx.cpp
Normal file
13
libraries/lib-strings/wxArrayStringEx.cpp
Normal file
@@ -0,0 +1,13 @@
|
||||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
@file wxArrayStringEx.cpp
|
||||
|
||||
Paul Licameli split from MemoryX.h
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
// This .cpp exists just to ensure there is at least one that includes
|
||||
// the header before any other header, and compiles
|
||||
#include "wxArrayStringEx.h"
|
||||
73
libraries/lib-strings/wxArrayStringEx.h
Normal file
73
libraries/lib-strings/wxArrayStringEx.h
Normal file
@@ -0,0 +1,73 @@
|
||||
/**********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
@file wxArrayStringEx.h
|
||||
|
||||
Paul Licameli split from MemoryX.h
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef __AUDACITY_WX_ARRAY_STRING_EX__
|
||||
#define __AUDACITY_WX_ARRAY_STRING_EX__
|
||||
|
||||
#include <wx/arrstr.h>
|
||||
|
||||
//! Extend wxArrayString with move operations and construction and insertion fromstd::initializer_list
|
||||
class wxArrayStringEx : public wxArrayString
|
||||
{
|
||||
public:
|
||||
using wxArrayString::wxArrayString;
|
||||
wxArrayStringEx() = default;
|
||||
|
||||
template< typename Iterator >
|
||||
wxArrayStringEx( Iterator start, Iterator finish )
|
||||
{
|
||||
this->reserve( std::distance( start, finish ) );
|
||||
while( start != finish )
|
||||
this->push_back( *start++ );
|
||||
}
|
||||
|
||||
template< typename T >
|
||||
wxArrayStringEx( std::initializer_list< T > items )
|
||||
{
|
||||
this->reserve( this->size() + items.size() );
|
||||
for ( const auto &item : items )
|
||||
this->push_back( item );
|
||||
}
|
||||
|
||||
//! The move operations can take arguments of the base class wxArrayString
|
||||
wxArrayStringEx( wxArrayString &&other )
|
||||
{
|
||||
swap( other );
|
||||
}
|
||||
|
||||
//! The move operations can take arguments of the base class wxArrayString
|
||||
wxArrayStringEx &operator= ( wxArrayString &&other )
|
||||
{
|
||||
if ( this != &other ) {
|
||||
clear();
|
||||
swap( other );
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
using wxArrayString::insert;
|
||||
|
||||
template< typename T >
|
||||
iterator insert( const_iterator pos, std::initializer_list< T > items )
|
||||
{
|
||||
const auto index = pos - ((const wxArrayString*)this)->begin();
|
||||
this->wxArrayString::Insert( {}, index, items.size() );
|
||||
auto result = this->begin() + index, iter = result;
|
||||
for ( auto pItem = items.begin(), pEnd = items.end();
|
||||
pItem != pEnd;
|
||||
++pItem, ++iter
|
||||
) {
|
||||
*iter = *pItem;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user