mirror of
https://github.com/cookiengineer/audacity
synced 2025-06-17 08:30:06 +02:00
... Format arguments are substituted into the translation of the msgid, which may not be known at the time the format arguments are captured (because locale may change). This allows TranslatableString with arguments to be constructed at static initialization time. There is also a special "verbatim" or null context which makes no translations of msgids. There is not yet any use of other contexts besides default or null.
326 lines
9.2 KiB
C++
326 lines
9.2 KiB
C++
/**********************************************************************
|
|
|
|
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 "Experimental.h"
|
|
#include "MemoryX.h"
|
|
|
|
#include <wx/log.h>
|
|
#include <wx/intl.h>
|
|
#include <wx/filename.h>
|
|
|
|
#include <locale.h>
|
|
#include <math.h> // for pow()
|
|
|
|
#include "../include/audacity/ComponentInterface.h"
|
|
|
|
// 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
|
|
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.
|
|
AUDACITY_DLL_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
|
|
AUDACITY_DLL_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.
|
|
AUDACITY_DLL_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)
|
|
exclude.push_back(wxString{ cc });
|
|
|
|
// The path separators may not be forbidden, so add them
|
|
auto separators = wxFileName::GetPathSeparators(format);
|
|
|
|
for(auto cc: separators) {
|
|
if (forbid.Find(cc) == wxNOT_FOUND)
|
|
exclude.push_back(wxString{ cc });
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
wxString 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);
|
|
}
|
|
|
|
wxString Internat::FormatSize(double size)
|
|
{
|
|
wxString sizeStr;
|
|
|
|
if (size == -1)
|
|
sizeStr = _("Unable to determine");
|
|
else {
|
|
/* make it look nice, by formatting into k, MB, etc */
|
|
if (size < 1024.0)
|
|
sizeStr = ToDisplayString(size) + wxT(" ") + _("bytes");
|
|
else if (size < 1024.0 * 1024.0) {
|
|
/* i18n-hint: Abbreviation for Kilo bytes */
|
|
sizeStr = ToDisplayString(size / 1024.0, 1) + wxT(" ") + _("KB");
|
|
}
|
|
else if (size < 1024.0 * 1024.0 * 1024.0) {
|
|
/* i18n-hint: Abbreviation for Mega bytes */
|
|
sizeStr = ToDisplayString(size / (1024.0 * 1024.0), 1) + wxT(" ") + _("MB");
|
|
}
|
|
else {
|
|
/* i18n-hint: Abbreviation for Giga bytes */
|
|
sizeStr = ToDisplayString(size / (1024.0 * 1024.0 * 1024.0), 1) + wxT(" ") + _("GB");
|
|
}
|
|
}
|
|
|
|
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 subsitution.
|
|
name.Replace(wxT("/"), wxT(":"));
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
wxString Internat::StripAccelerators(const wxString &s)
|
|
{
|
|
wxString result;
|
|
result.reserve(s.length());
|
|
for(size_t i = 0; i < s.length(); i++) {
|
|
if (s[i] == '\t')
|
|
break;
|
|
if (s[i] != '&' && s[i] != '.')
|
|
result += s[i];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
wxArrayStringEx LocalizedStrings(
|
|
const EnumValueSymbol strings[], size_t nStrings)
|
|
{
|
|
return transform_range<wxArrayStringEx>(
|
|
strings, strings + nStrings,
|
|
std::mem_fn( &EnumValueSymbol::Translation )
|
|
);
|
|
}
|
|
|
|
// Find a better place for this?
|
|
#include "audacity/Types.h"
|
|
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() };
|
|
}
|
|
|
|
static const wxChar *const NullContextName = wxT("*");
|
|
|
|
const TranslatableString::Formatter
|
|
TranslatableString::NullContextFormatter {
|
|
[](const wxString & str) -> wxString {
|
|
if (str.empty())
|
|
return NullContextName;
|
|
else
|
|
return str;
|
|
}
|
|
};
|
|
|
|
bool TranslatableString::IsVerbatim() const
|
|
{
|
|
return mFormatter && mFormatter({}) == NullContextName;
|
|
}
|
|
|
|
wxString TranslatableString::Translation() const
|
|
{
|
|
wxString context;
|
|
if ( mFormatter )
|
|
context = mFormatter({});
|
|
|
|
wxString result = (context == NullContextName)
|
|
? *this
|
|
: wxGetTranslation( *this, {}, context );
|
|
|
|
if ( mFormatter )
|
|
result = mFormatter( result );
|
|
|
|
return result;
|
|
}
|