1
0
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:
Paul Licameli
2021-02-16 21:14:33 -05:00
parent 70c4898648
commit 45c6190c51
37 changed files with 75 additions and 50 deletions

View File

@@ -4,6 +4,7 @@
# that it depends on
set( LIBRARIES
"lib-string-utils"
lib-strings
)
if ( ${_OPT}has_networking )

View 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}"
"" ""
)

View 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() };
}

View 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

View 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;
}

View 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

View 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 {};
}
}

View 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__

View 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") };

View 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

View 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

View 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"

View 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