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