1 /**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 Languages.cpp
6
7 Dominic Mazzoni
8
9
10 *******************************************************************//*!
11
12 \file Languages.cpp
13 \brief Determine installed languages.
14
15 Figure out what translations are installed and return a list
16 of language codes (like "es", "fr", or "pt-br") and corresponding
17 language names (like "Español", "Français", and "Português").
18 We use our own list of translations of language names (i.e.
19 "Français" instead of "French") but we fallback on the language
20 name in wxWidgets if we don't have it listed.
21
22 This code is designed to work well with all of the current
23 languages, but adapt to any language that wxWidgets supports.
24 Other languages will only be supported if they're added to
25 the database using wxLocale::AddLanguage.
26
27 But for the most part, this means that somebody could add a NEW
28 translation and have it work immediately.
29
30 *//*******************************************************************/
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
52 using LangHash = std::unordered_map<wxString, TranslatableString>;
53 using ReverseLangHash = std::unordered_map<TranslatableString, wxString>;
54
FindFilesInPathList(const wxString & pattern,const FilePaths & pathList,FilePaths & results)55 static 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
TranslationExists(const FilePaths & pathList,wxString code)65 static 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/CFLocale.h>
78 #include <wx/osx/core/cfstring.h>
79 #endif
80
81 namespace Languages {
82
GetSystemLanguageCode(const FilePaths & pathList)83 wxString GetSystemLanguageCode(const FilePaths &pathList)
84 {
85 wxArrayString langCodes;
86 TranslatableStrings langNames;
87
88 GetLanguages(pathList, langCodes, langNames);
89
90 int sysLang = wxLocale::GetSystemLanguage();
91
92 const wxLanguageInfo *info;
93
94 #ifdef __WXMAC__
95 // PRL: Bug 1227, system language on Mac may not be right because wxW3 is
96 // dependent on country code too in wxLocale::GetSystemLanguage().
97
98 if (sysLang == wxLANGUAGE_UNKNOWN)
99 {
100 // wxW3 did a too-specific lookup of language and country, when
101 // there is nothing for that combination; try it by language alone.
102
103 // The following lines are cribbed from that function.
104 wxCFRef<CFLocaleRef> userLocaleRef(CFLocaleCopyCurrent());
105 wxCFStringRef str(wxCFRetain((CFStringRef)CFLocaleGetValue(userLocaleRef, kCFLocaleLanguageCode)));
106 auto lang = str.AsString();
107
108 // Now avoid wxLocale::GetLanguageInfo(), instead calling:
109 info = wxLocale::FindLanguageInfo(lang);
110 }
111 else
112 #endif
113 {
114 info = wxLocale::GetLanguageInfo(sysLang);
115 }
116
117 if (info) {
118 wxString fullCode = info->CanonicalName;
119 if (fullCode.length() < 2)
120 return wxT("en");
121
122 wxString code = fullCode.Left(2);
123 unsigned int i;
124
125 for(i=0; i<langCodes.size(); i++) {
126 if (langCodes[i] == fullCode)
127 return fullCode;
128
129 if (langCodes[i] == code)
130 return code;
131 }
132 }
133
134 return wxT("en");
135 }
136
GetLanguages(FilePaths pathList,wxArrayString & langCodes,TranslatableStrings & langNames)137 void GetLanguages( FilePaths pathList,
138 wxArrayString &langCodes, TranslatableStrings &langNames)
139 {
140 static const char *const utf8Names[] = {
141 "af Afrikaans",
142 "ar \330\247\331\204\330\271\330\261\330\250\331\212\330\251",
143 "be \320\221\320\265\320\273\320\260\321\200\321\203\321\201\320\272\320\260\321\217",
144 "bg \320\221\321\212\320\273\320\263\320\260\321\200\321\201\320\272\320\270",
145 "bn \340\246\254\340\246\276\340\246\202\340\246\262\340\246\276",
146 "bs Bosanski",
147 "ca Catal\303\240",
148 "ca_ES@valencia Valenci\303\240",
149 "co Corsu",
150 "cs \304\214e\305\241tina",
151 "cy Cymraeg",
152 "da Dansk",
153 "de Deutsch",
154 "el \316\225\316\273\316\273\316\267\316\275\316\271\316\272\316\254",
155 "en English",
156 "es Espa\303\261ol",
157 "eu Euskara",
158 "eu_ES Euskara (Espainiako)",
159 "fa \331\201\330\247\330\261\330\263\333\214",
160 "fi Suomi",
161 "fr Fran\303\247ais",
162 "ga Gaeilge",
163 "gl Galego",
164 "he \327\242\327\221\327\250\327\231\327\252",
165 "hi \340\244\271\340\244\277\340\244\250\340\245\215\340\244\246\340\245\200",
166 "hr Hrvatski",
167 "hu Magyar",
168 "hy \325\200\325\241\325\265\325\245\326\200\325\245\325\266",
169 "id Bahasa Indonesia",
170 "it Italiano",
171 "ja \346\227\245\346\234\254\350\252\236",
172 "ka \341\203\245\341\203\220\341\203\240\341\203\227\341\203\243\341\203\232\341\203\230",
173 "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",
174 "ko \355\225\234\352\265\255\354\226\264",
175 "lt Lietuvi\305\263",
176 "mk \320\234\320\260\320\272\320\265\320\264\320\276\320\275\321\201\320\272\320\270",
177 "mr \340\244\256\340\244\260\340\244\276\340\244\240\340\245\200",
178 "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",
179 "nb Norsk",
180 "nl Nederlands",
181 "oc Occitan",
182 "pl Polski",
183 "pt Portugu\303\252s",
184 "pt_BR Portugu\303\252s (Brasil)",
185 "ro Rom\303\242n\304\203",
186 "ru \320\240\321\203\321\201\321\201\320\272\320\270\320\271",
187 "sk Sloven\304\215ina",
188 "sl Sloven\305\241\304\215ina",
189 "sr_RS \320\241\321\200\320\277\321\201\320\272\320\270",
190 "sr_RS@latin Srpski",
191 "sv Svenska",
192 "ta \340\256\244\340\256\256\340\256\277\340\256\264\340\257\215",
193 "tg \320\242\320\276\322\267\320\270\320\272\323\243",
194 "tr T\303\274rk\303\247e",
195 "uk \320\243\320\272\321\200\320\260\321\227\320\275\321\201\321\214\320\272\320\260",
196 "vi Ti\341\272\277ng Vi\341\273\207t",
197 "zh_CN \344\270\255\346\226\207\357\274\210\347\256\200\344\275\223\357\274\211",
198 "zh_TW \344\270\255\346\226\207\357\274\210\347\271\201\351\253\224\357\274\211",
199 };
200
201 TranslatableStrings tempNames;
202 wxArrayString tempCodes;
203 ReverseLangHash reverseHash;
204 LangHash tempHash;
205
206 const LangHash localLanguageName = []{
207 LangHash localLanguageName;
208 for ( auto utf8Name : utf8Names )
209 {
210 auto str = wxString::FromUTF8(utf8Name);
211 auto code = str.BeforeFirst(' ');
212 auto name = str.AfterFirst(' ');
213 localLanguageName[code] = Verbatim( name );
214 }
215 return localLanguageName;
216 }();
217
218 #if defined(__WXGTK__)
219 {
220 wxFileName pathNorm{ wxStandardPaths::Get().GetInstallPrefix() + L"/share/locale" };
221 pathNorm.Normalize();
222 const wxString newPath{ pathNorm.GetFullPath() };
223 if (pathList.end() ==
224 std::find(pathList.begin(), pathList.end(), newPath))
225 pathList.push_back(newPath);
226 }
227 #endif
228
229 // For each language in our list we look for a corresponding entry in
230 // wxLocale.
231 for ( auto end = localLanguageName.end(), i = localLanguageName.begin();
232 i != end; ++i )
233 {
234 const wxLanguageInfo *info = wxLocale::FindLanguageInfo(i->first);
235
236 if (!info) {
237 wxASSERT(info != NULL);
238 continue;
239 }
240
241 wxString fullCode = info->CanonicalName;
242 wxString code = fullCode.Left(2);
243 auto name = Verbatim( info->Description );
244
245 // Logic: Languages codes are sometimes hierarchical, with a
246 // general language code and then a subheading. For example,
247 // zh_TW for Traditional Chinese and zh_CN for Simplified
248 // Chinese - but just zh for Chinese in general. First we look
249 // for the full code, like zh_TW. If that doesn't exist, we
250 // look for a code corresponding to the first two letters.
251 // Note that if the language for a fullCode exists but we only
252 // have a name for the short code, we will use the short code's
253 // name but associate it with the full code. This allows someone
254 // to drop in a NEW language and still get reasonable behavior.
255
256 if (fullCode.length() < 2)
257 continue;
258
259 auto found = localLanguageName.find( code );
260 if ( found != end ) {
261 name = found->second;
262 }
263 found = localLanguageName.find( fullCode );
264 if ( found != end ) {
265 name = found->second;
266 }
267
268 if (TranslationExists(pathList, fullCode)) {
269 code = fullCode;
270 }
271
272 if (!tempHash[code].empty())
273 continue;
274
275 if (TranslationExists(pathList, code) || code==wxT("en")) {
276 tempCodes.push_back(code);
277 tempNames.push_back(name);
278 tempHash[code] = name;
279
280 /* wxLogDebug(wxT("code=%s name=%s fullCode=%s name=%s -> %s"),
281 code, localLanguageName[code],
282 fullCode, localLanguageName[fullCode],
283 name);*/
284 }
285 }
286
287 // JKC: Adding language for simplified audacity.
288 {
289 wxString code;
290 code = wxT("en-simple");
291 auto name = XO("Simplified");
292 if (TranslationExists(pathList, code) ) {
293 tempCodes.push_back(code);
294 tempNames.push_back(name);
295 tempHash[code] = name;
296 }
297 }
298
299
300 // Sort
301 unsigned int j;
302 for(j=0; j<tempNames.size(); j++){
303 reverseHash[tempNames[j]] = tempCodes[j];
304 }
305
306 std::sort( tempNames.begin(), tempNames.end(),
307 []( const TranslatableString &a, const TranslatableString &b ){
308 return a.Translation() < b.Translation();
309 } );
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
321 static std::unique_ptr<wxLocale> sLocale;
322 static wxString sLocaleName;
323
SetLang(const FilePaths & pathList,const wxString & lang)324 wxString 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
374 Internat::Init();
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
GetLocaleName()390 wxString GetLocaleName()
391 {
392 return sLocaleName;
393 }
394
GetLang()395 wxString GetLang()
396 {
397 if (sLocale)
398 return sLocale->GetSysName();
399 else
400 return {};
401 }
402
GetLangShort()403 wxString GetLangShort()
404 {
405 if (sLocale)
406 return sLocale->GetName();
407 else
408 return {};
409 }
410 }
411