Audacity 3.2.0
Languages.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 Languages.cpp
6
7 Dominic Mazzoni
8
9
10*******************************************************************//*******************************************************************/
31
32
33
34#include "Languages.h"
35#include <memory>
36#include "wxArrayStringEx.h"
37
38#include "Internat.h"
39#include "wxArrayStringEx.h"
40
41#include <wx/defs.h>
42#include <wx/dir.h>
43#include <wx/filename.h>
44#include <wx/intl.h>
45#include <wx/stdpaths.h>
46#include <wx/textfile.h>
47#include <wx/utils.h> // for wxSetEnv
48
49#include <clocale>
50#include <unordered_map>
51
52using LangHash = std::unordered_map<wxString, TranslatableString>;
53using ReverseLangHash = std::unordered_map<TranslatableString, wxString>;
54
55static void FindFilesInPathList(const wxString & pattern,
56 const FilePaths & pathList, FilePaths & results)
57{
58 wxFileName ff;
59 for (const auto &path : pathList) {
60 ff = path + wxFILE_SEP_PATH + pattern;
61 wxDir::GetAllFiles(ff.GetPath(), &results, ff.GetFullName(), wxDIR_FILES);
62 }
63}
64
65static bool TranslationExists(const FilePaths &pathList, wxString code)
66{
67 FilePaths results;
68 FindFilesInPathList(code + L"/audacity.mo", pathList, results);
69#if defined(__WXMAC__)
70 FindFilesInPathList(code + L".lproj/audacity.mo", pathList, results);
71#endif
72 FindFilesInPathList(code + L"/LC_MESSAGES/audacity.mo", pathList, results);
73 return (results.size() > 0);
74}
75
76#ifdef __WXMAC__
77#include <CoreFoundation/CFPreferences.h>
78#include <wx/osx/core/cfstring.h>
79#endif
80
81namespace Languages {
82
83wxString GetSystemLanguageCode(const FilePaths &pathList)
84{
85 wxArrayString langCodes;
86 TranslatableStrings langNames;
87
88 GetLanguages(pathList, langCodes, langNames);
89
90 const wxLanguageInfo *info;
91
92#ifdef __WXMAC__
93 // https://github.com/audacity/audacity/issues/2493
94 // It was observed, that macOS can have multiple `system default` languages,
95 // depending on the application rather than the user preferences.
96 // As a workaround, let's query the locale preferences for the application.
97 const wxCFStringRef appleLocale((CFStringRef)CFPreferencesCopyAppValue(CFSTR("AppleLocale"),
98 kCFPreferencesCurrentApplication));
99
100 const auto localeString = appleLocale.AsString();
101 // AppleLocale has `Apple` locale format, but let us try our luck
102 // and pass it FindLanguageInfo, so we can get the best possible match.
103 info = wxLocale::FindLanguageInfo(localeString);
104
105 if (info == nullptr)
106 {
107 // Full match has failed, lets match the language code only.
108 // wxLocale expects a two symbol code
109 wxString langCode = localeString.Left(2);
110 info = wxLocale::FindLanguageInfo(langCode);
111 }
112#else
113 {
114
115 const auto sysLang = wxLocale::GetSystemLanguage();
116 info = wxLocale::GetLanguageInfo(sysLang);
117 }
118#endif
119
120 if (info) {
121 wxString fullCode = info->CanonicalName;
122 if (fullCode.length() < 2)
123 return wxT("en");
124
125 wxString code = fullCode.Left(2);
126 unsigned int i;
127
128 for(i=0; i<langCodes.size(); i++) {
129 if (langCodes[i] == fullCode)
130 return fullCode;
131
132 if (langCodes[i] == code)
133 return code;
134 }
135 }
136
137 return wxT("en");
138}
139
140void GetLanguages( FilePaths pathList,
141 wxArrayString &langCodes, TranslatableStrings &langNames)
142{
143 static const char *const utf8Names[] = {
144"af Afrikaans",
145"ar \330\247\331\204\330\271\330\261\330\250\331\212\330\251",
146"be \320\221\320\265\320\273\320\260\321\200\321\203\321\201\320\272\320\260\321\217",
147"bg \320\221\321\212\320\273\320\263\320\260\321\200\321\201\320\272\320\270",
148"bn \340\246\254\340\246\276\340\246\202\340\246\262\340\246\276",
149"bs Bosanski",
150"ca Catal\303\240",
151"ca_ES@valencia Valenci\303\240",
152"co Corsu",
153"cs \304\214e\305\241tina",
154"cy Cymraeg",
155"da Dansk",
156"de Deutsch",
157"el \316\225\316\273\316\273\316\267\316\275\316\271\316\272\316\254",
158"en English",
159"es Espa\303\261ol",
160"eu Euskara",
161"eu_ES Euskara (Espainiako)",
162"fa \331\201\330\247\330\261\330\263\333\214",
163"fi Suomi",
164"fr Fran\303\247ais",
165"ga Gaeilge",
166"gl Galego",
167"he \327\242\327\221\327\250\327\231\327\252",
168"hi \340\244\271\340\244\277\340\244\250\340\245\215\340\244\246\340\245\200",
169"hr Hrvatski",
170"hu Magyar",
171"hy \325\200\325\241\325\265\325\245\326\200\325\245\325\266",
172"id Bahasa Indonesia",
173"it Italiano",
174"ja \346\227\245\346\234\254\350\252\236",
175"ka \341\203\245\341\203\220\341\203\240\341\203\227\341\203\243\341\203\232\341\203\230",
176"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",
177"ko \355\225\234\352\265\255\354\226\264",
178"lt Lietuvi\305\263",
179"mk \320\234\320\260\320\272\320\265\320\264\320\276\320\275\321\201\320\272\320\270",
180"mr \340\244\256\340\244\260\340\244\276\340\244\240\340\245\200",
181"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",
182"nb Norsk",
183"nl Nederlands",
184"oc Occitan",
185"pl Polski",
186"pt Portugu\303\252s",
187"pt_BR Portugu\303\252s (Brasil)",
188"ro Rom\303\242n\304\203",
189"ru \320\240\321\203\321\201\321\201\320\272\320\270\320\271",
190"sk Sloven\304\215ina",
191"sl Sloven\305\241\304\215ina",
192"sr_RS \320\241\321\200\320\277\321\201\320\272\320\270",
193"sr_RS@latin Srpski",
194"sv Svenska",
195"ta \340\256\244\340\256\256\340\256\277\340\256\264\340\257\215",
196"tg \320\242\320\276\322\267\320\270\320\272\323\243",
197"tr T\303\274rk\303\247e",
198"uk \320\243\320\272\321\200\320\260\321\227\320\275\321\201\321\214\320\272\320\260",
199"vi Ti\341\272\277ng Vi\341\273\207t",
200"zh_CN \344\270\255\346\226\207\357\274\210\347\256\200\344\275\223\357\274\211",
201"zh_TW \344\270\255\346\226\207\357\274\210\347\271\201\351\253\224\357\274\211",
202 };
203
204 TranslatableStrings tempNames;
205 wxArrayString tempCodes;
206 ReverseLangHash reverseHash;
207 LangHash tempHash;
208
209 const LangHash localLanguageName = []{
210 LangHash localLanguageName;
211 for ( auto utf8Name : utf8Names )
212 {
213 auto str = wxString::FromUTF8(utf8Name);
214 auto code = str.BeforeFirst(' ');
215 auto name = str.AfterFirst(' ');
216 localLanguageName[code] = Verbatim( name );
217 }
218 return localLanguageName;
219 }();
220
221#if defined(__WXGTK__)
222 {
223 wxFileName pathNorm{ wxStandardPaths::Get().GetInstallPrefix() + L"/share/locale" };
224 pathNorm.Normalize();
225 const wxString newPath{ pathNorm.GetFullPath() };
226 if (pathList.end() ==
227 std::find(pathList.begin(), pathList.end(), newPath))
228 pathList.push_back(newPath);
229 }
230#endif
231
232 // For each language in our list we look for a corresponding entry in
233 // wxLocale.
234 for ( auto end = localLanguageName.end(), i = localLanguageName.begin();
235 i != end; ++i )
236 {
237 const wxLanguageInfo *info = wxLocale::FindLanguageInfo(i->first);
238
239 if (!info) {
240 wxASSERT(info != NULL);
241 continue;
242 }
243
244 wxString fullCode = info->CanonicalName;
245 wxString code = fullCode.Left(2);
246 auto name = Verbatim( info->Description );
247
248 // Logic: Languages codes are sometimes hierarchical, with a
249 // general language code and then a subheading. For example,
250 // zh_TW for Traditional Chinese and zh_CN for Simplified
251 // Chinese - but just zh for Chinese in general. First we look
252 // for the full code, like zh_TW. If that doesn't exist, we
253 // look for a code corresponding to the first two letters.
254 // Note that if the language for a fullCode exists but we only
255 // have a name for the short code, we will use the short code's
256 // name but associate it with the full code. This allows someone
257 // to drop in a NEW language and still get reasonable behavior.
258
259 if (fullCode.length() < 2)
260 continue;
261
262 auto found = localLanguageName.find( code );
263 if ( found != end ) {
264 name = found->second;
265 }
266 found = localLanguageName.find( fullCode );
267 if ( found != end ) {
268 name = found->second;
269 }
270
271 if (TranslationExists(pathList, fullCode)) {
272 code = fullCode;
273 }
274
275 if (!tempHash[code].empty())
276 continue;
277
278 if (TranslationExists(pathList, code) || code==wxT("en")) {
279 tempCodes.push_back(code);
280 tempNames.push_back(name);
281 tempHash[code] = name;
282
283/* wxLogDebug(wxT("code=%s name=%s fullCode=%s name=%s -> %s"),
284 code, localLanguageName[code],
285 fullCode, localLanguageName[fullCode],
286 name);*/
287 }
288 }
289
290 // JKC: Adding language for simplified audacity.
291 {
292 wxString code;
293 code = wxT("en-simple");
294 auto name = XO("Simplified");
295 if (TranslationExists(pathList, code) ) {
296 tempCodes.push_back(code);
297 tempNames.push_back(name);
298 tempHash[code] = name;
299 }
300 }
301
302
303 // Sort
304 unsigned int j;
305 for(j=0; j<tempNames.size(); j++){
306 reverseHash[tempNames[j]] = tempCodes[j];
307 }
308
309 std::sort(tempNames.begin(), tempNames.end(), TranslationLess);
310
311 // Add system language
312 langNames.push_back(XO("System"));
313 langCodes.push_back(wxT("System"));
314
315 for(j=0; j<tempNames.size(); j++) {
316 langNames.push_back(tempNames[j]);
317 langCodes.push_back(reverseHash[tempNames[j]]);
318 }
319}
320
321static std::unique_ptr<wxLocale> sLocale;
322static wxString sLocaleName;
323
324wxString SetLang( const FilePaths &pathList, const wxString & lang )
325{
326 wxString result = lang;
327
328 sLocale.reset();
329
330#if defined(__WXMAC__)
331 // This should be reviewed again during the wx3 conversion.
332
333 // On OSX, if the LANG environment variable isn't set when
334 // using a language like Japanese, an assertion will trigger
335 // because conversion to Japanese from "?" doesn't return a
336 // valid length, so make OSX happy by defining/overriding
337 // the LANG environment variable with U.S. English for now.
338 wxSetEnv(wxT("LANG"), wxT("en_US.UTF-8"));
339#endif
340
341 const wxLanguageInfo *info = NULL;
342 if (!lang.empty() && lang != wxT("System")) {
343 // Try to find the given language
344 info = wxLocale::FindLanguageInfo(lang);
345 }
346 if (!info)
347 {
348 // Not given a language or can't find it; substitute the system language
349 result = Languages::GetSystemLanguageCode(pathList);
350 info = wxLocale::FindLanguageInfo(result);
351 if (!info)
352 // Return the substituted system language, but we can't complete setup
353 // Should we try to do something better?
354 return result;
355 }
356 sLocale = std::make_unique<wxLocale>(info->Language);
357
358 for( const auto &path : pathList )
359 sLocale->AddCatalogLookupPathPrefix( path );
360
361 // LL: Must add the wxWidgets catalog manually since the search
362 // paths were not set up when mLocale was created. The
363 // catalogs are search in LIFO order, so add wxstd first.
364 sLocale->AddCatalog(wxT("wxstd"));
365
366 // Must match TranslationExists() in Languages.cpp
367 sLocale->AddCatalog("audacity");
368
369 // Initialize internationalisation (number formats etc.)
370 //
371 // This must go _after_ creating the wxLocale instance because
372 // creating the wxLocale instance sets the application-wide locale.
373
375
376 using future1 = decltype(
377 // The file of unused strings is part of the source tree scanned by
378 // xgettext when compiling the catalog template audacity.pot.
379 // Including it here doesn't change that but does make the C++ compiler
380 // check for correct syntax, but also generate no object code for them.
381#include "FutureStrings.h"
382 0
383 );
384
385 sLocaleName = wxSetlocale(LC_ALL, NULL);
386
387 return result;
388}
389
391{
392 return sLocaleName;
393}
394
395wxString GetLang()
396{
397 if (sLocale)
398 return sLocale->GetSysName();
399 else
400 return {};
401}
402
403wxString GetLangShort()
404{
405 if (sLocale)
406 return sLocale->GetName();
407 else
408 return {};
409}
410}
wxT("CloseDown"))
#define str(a)
XO("Cut/Copy/Paste")
std::unordered_map< TranslatableString, wxString > ReverseLangHash
Definition: Languages.cpp:53
static bool TranslationExists(const FilePaths &pathList, wxString code)
Definition: Languages.cpp:65
std::unordered_map< wxString, TranslatableString > LangHash
Definition: Languages.cpp:52
static void FindFilesInPathList(const wxString &pattern, const FilePaths &pathList, FilePaths &results)
Definition: Languages.cpp:55
wxString name
Definition: TagsEditor.cpp:166
bool TranslationLess(const TranslatableString &a, const TranslatableString &b)
A commonly needed sort comparator, which depends on the language setting.
TranslatableString Verbatim(wxString str)
Require calls to the one-argument constructor to go through this distinct global function name.
std::vector< TranslatableString > TranslatableStrings
static void Init()
Initialize internationalisation support. Call this once at program start.
Definition: Internat.cpp:53
Extend wxArrayString with move operations and construction and insertion fromstd::initializer_list.
Services * Get()
Fetch the global instance, or nullptr if none is yet installed.
Definition: BasicUI.cpp:202
wxString GetSystemLanguageCode(const FilePaths &pathList)
Definition: Languages.cpp:83
wxString GetLang()
Definition: Languages.cpp:395
static std::unique_ptr< wxLocale > sLocale
Definition: Languages.cpp:321
wxString GetLocaleName()
Definition: Languages.cpp:390
static wxString sLocaleName
Definition: Languages.cpp:322
void GetLanguages(FilePaths pathList, wxArrayString &langCodes, TranslatableStrings &langNames)
Definition: Languages.cpp:140
wxString GetLangShort()
Definition: Languages.cpp:403
wxString SetLang(const FilePaths &pathList, const wxString &lang)
Definition: Languages.cpp:324
const char * end(const char *str) noexcept
Definition: StringUtils.h:106