OSDN Git Service

ShellExtension: Fix an issue where the Shell Extension menu string did not switch...
[winmerge-jp/winmerge-jp.git] / ShellExtension / LanguageSelect.cpp
1 /**
2  * @file  LanguageSelect.cpp
3  *
4  * @brief Implements the Language Selection dialog class (which contains the language data)
5  */
6
7 #include "StdAfx.h"
8 #include "LanguageSelect.h"
9
10 // Escaped character constants in range 0x80-0xFF are interpreted in current codepage
11 // Using C locale gets us direct mapping to Unicode codepoints
12 #pragma setlocale("C")
13
14 #ifdef _DEBUG
15 #define new DEBUG_NEW
16 #endif
17
18 static wchar_t *EatPrefix(wchar_t *text, const wchar_t *prefix);
19 static void unslash(std::wstring &s);
20 static HANDLE NTAPI FindFile(HANDLE h, LPCTSTR path, WIN32_FIND_DATA *fd);
21
22 /**
23  * @brief A class holding information about language file.
24  */
25 class LangFileInfo
26 {
27 public:
28         LANGID id; /**< Language ID. */
29
30         static LANGID LangId(const char *lang, const char *sublang);
31         
32         /**
33          * A constructor taking a language id as parameter.
34          * @param [in] id Language ID to use.
35          */
36         explicit LangFileInfo(LANGID id): id(id) { };
37         
38         explicit LangFileInfo(LPCTSTR path);
39         std::wstring GetString(LCTYPE type) const;
40
41 private:
42         struct rg
43         {
44                 LANGID id;
45                 const char *lang;
46         };
47         static const struct rg rg[];
48 };
49
50 /**
51  * @brief An array holding language IDs and names.
52  */
53 const struct LangFileInfo::rg LangFileInfo::rg[] =
54 {
55         {
56                 LANG_AFRIKAANS,         "AFRIKAANS\0"
57         },
58         {
59                 LANG_ALBANIAN,          "ALBANIAN\0"
60         },
61         {
62                 LANG_ARABIC,            "ARABIC\0"                                              "SAUDI_ARABIA\0"
63                                                                                                                         "IRAQ\0"
64                                                                                                                         "EGYPT\0"
65                                                                                                                         "LIBYA\0"
66                                                                                                                         "ALGERIA\0"
67                                                                                                                         "MOROCCO\0"
68                                                                                                                         "TUNISIA\0"
69                                                                                                                         "OMAN\0"
70                                                                                                                         "YEMEN\0"
71                                                                                                                         "SYRIA\0"
72                                                                                                                         "JORDAN\0"
73                                                                                                                         "LEBANON\0"
74                                                                                                                         "KUWAIT\0"
75                                                                                                                         "UAE\0"
76                                                                                                                         "BAHRAIN\0"
77                                                                                                                         "QATAR\0"
78         },
79         {
80                 LANG_ARMENIAN,          "ARMENIAN\0"
81         },
82         {
83                 LANG_ASSAMESE,          "ASSAMESE\0"
84         },
85         {
86                 LANG_AZERI,                     "AZERI\0"                                               "LATIN\0"
87                                                                                                                         "CYRILLIC\0"
88         },
89         {
90                 LANG_BASQUE,            "BASQUE\0"
91         },
92         {
93                 LANG_BELARUSIAN,        "BELARUSIAN\0"
94         },
95         {
96                 LANG_BENGALI,           "BENGALI\0"
97         },
98         {
99                 LANG_BULGARIAN,         "BULGARIAN\0"
100         },
101         {
102                 LANG_CATALAN,           "CATALAN\0"
103         },
104         {
105                 LANG_CHINESE,           "CHINESE\0"                                             "TRADITIONAL\0"
106                                                                                                                         "SIMPLIFIED\0"
107                                                                                                                         "HONGKONG\0"
108                                                                                                                         "SINGAPORE\0"
109                                                                                                                         "MACAU\0"
110         },
111         {
112                 LANG_CROATIAN,          "CROATIAN\0"
113         },
114         {
115                 LANG_CZECH,                     "CZECH\0"
116         },
117         {
118                 LANG_DANISH,            "DANISH\0"
119         },
120         {
121                 LANG_DIVEHI,            "DIVEHI\0"
122         },
123         {
124                 MAKELANGID(LANG_DUTCH, SUBLANG_DUTCH),                          "DUTCH\0"
125                                                                                                                         "BELGIAN\0"
126         },
127         {
128                 LANG_ENGLISH,           "ENGLISH\0"                                             "US\0"
129                                                                                                                         "UK\0"
130                                                                                                                         "AUS\0"
131                                                                                                                         "CAN\0"
132                                                                                                                         "NZ\0"
133                                                                                                                         "EIRE\0"
134                                                                                                                         "SOUTH_AFRICA\0"
135                                                                                                                         "JAMAICA\0"
136                                                                                                                         "CARIBBEAN\0"
137                                                                                                                         "BELIZE\0"
138                                                                                                                         "TRINIDAD\0"
139                                                                                                                         "ZIMBABWE\0"
140                                                                                                                         "PHILIPPINES\0"
141         },
142         {
143                 LANG_ESTONIAN,          "ESTONIAN\0"
144         },
145         {
146                 LANG_FAEROESE,          "FAEROESE\0"
147         },
148         {
149                 LANG_FARSI,                     "FARSI\0"
150         },
151         {
152                 LANG_FINNISH,           "FINNISH\0"
153         },
154         {
155                 MAKELANGID(LANG_FRENCH, SUBLANG_FRENCH),                        "FRENCH\0"
156                                                                                                                         "BELGIAN\0"
157                                                                                                                         "CANADIAN\0"
158                                                                                                                         "SWISS\0"
159                                                                                                                         "LUXEMBOURG\0"
160                                                                                                                         "MONACO\0"
161         },
162         {
163                 LANG_GALICIAN,          "GALICIAN\0"
164         },
165         {
166                 LANG_GEORGIAN,          "GEORGIAN\0"
167         },
168         {
169                 MAKELANGID(LANG_GERMAN, SUBLANG_GERMAN),                        "GERMAN\0"
170                                                                                                                         "SWISS\0"
171                                                                                                                         "AUSTRIAN\0"
172                                                                                                                         "LUXEMBOURG\0"
173                                                                                                                         "LIECHTENSTEIN"
174         },
175         {
176                 LANG_GREEK,                     "GREEK\0"
177         },
178         {
179                 LANG_GUJARATI,          "GUJARATI\0"
180         },
181         {
182                 LANG_HEBREW,            "HEBREW\0"
183         },
184         {
185                 LANG_HINDI,                     "HINDI\0"
186         },
187         {
188                 LANG_HUNGARIAN,         "HUNGARIAN\0"
189         },
190         {
191                 LANG_ICELANDIC,         "ICELANDIC\0"
192         },
193         {
194                 LANG_INDONESIAN,        "INDONESIAN\0"
195         },
196         {
197                 MAKELANGID(LANG_ITALIAN, SUBLANG_ITALIAN),                      "ITALIAN\0"
198                                                                                                                         "SWISS\0"
199         },
200         {
201                 LANG_JAPANESE,          "JAPANESE\0"
202         },
203         {
204                 LANG_KANNADA,           "KANNADA\0"
205         },
206         {
207                 MAKELANGID(LANG_KASHMIRI, SUBLANG_DEFAULT),                     "KASHMIRI\0"
208                                                                                                                         "SASIA\0"
209         },
210         {
211                 LANG_KAZAK,                     "KAZAK\0"
212         },
213         {
214                 LANG_KONKANI,           "KONKANI\0"
215         },
216         {
217                 MAKELANGID(LANG_KOREAN, SUBLANG_KOREAN),                        "KOREAN\0"
218         },
219         {
220                 LANG_KYRGYZ,            "KYRGYZ\0"
221         },
222         {
223                 LANG_LATVIAN,           "LATVIAN\0"
224         },
225         {
226                 MAKELANGID(LANG_LITHUANIAN, SUBLANG_LITHUANIAN),        "LITHUANIAN\0"
227         },
228         {
229                 LANG_MACEDONIAN,        "MACEDONIAN\0"
230         },
231         {
232                 LANG_MALAY,                     "MALAY\0"                                               "MALAYSIA\0"
233                                                                                                                         "BRUNEI_DARUSSALAM\0"
234         },
235         {
236                 LANG_MALAYALAM,         "MALAYALAM\0"
237         },
238         {
239                 LANG_MANIPURI,          "MANIPURI\0"
240         },
241         {
242                 LANG_MARATHI,           "MARATHI\0"
243         },
244         {
245                 LANG_MONGOLIAN,         "MONGOLIAN\0"
246         },
247         {
248                 MAKELANGID(LANG_NEPALI, SUBLANG_DEFAULT),                       "NEPALI\0"
249                                                                                                                         "INDIA\0"
250         },
251         {
252                 LANG_NORWEGIAN,         "NORWEGIAN\0"                                   "BOKMAL\0"
253                                                                                                                         "NYNORSK\0"
254         },
255         {
256                 LANG_ORIYA,                     "ORIYA\0"
257         },
258         {
259                 LANG_POLISH,            "POLISH\0"
260         },
261         {
262                 MAKELANGID(LANG_PORTUGUESE, SUBLANG_PORTUGUESE),        "PORTUGUESE\0"
263                                                                                                                         "BRAZILIAN\0"
264         },
265         {
266                 LANG_PUNJABI,           "PUNJABI\0"
267         },
268         {
269                 LANG_ROMANIAN,          "ROMANIAN\0"
270         },
271         {
272                 LANG_RUSSIAN,           "RUSSIAN\0"
273         },
274         {
275                 LANG_SANSKRIT,          "SANSKRIT\0"
276         },
277         {
278                 MAKELANGID(LANG_SERBIAN, SUBLANG_DEFAULT),                      "SERBIAN\0"
279                                                                                                                         "LATIN\0"
280                                                                                                                         "CYRILLIC\0"
281         },
282         {
283                 LANG_SINDHI,            "SINDHI\0"
284         },
285         {
286                 LANG_SINHALESE,         "SINHALESE\0"
287         },
288         {
289                 LANG_SLOVAK,            "SLOVAK\0"
290         },
291         {
292                 LANG_SLOVENIAN,         "SLOVENIAN\0"
293         },
294         {
295                 MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH),                      "SPANISH\0"
296                                                                                                                         "MEXICAN\0"
297                                                                                                                         "MODERN\0"
298                                                                                                                         "GUATEMALA\0"
299                                                                                                                         "COSTA_RICA\0"
300                                                                                                                         "PANAMA\0"
301                                                                                                                         "DOMINICAN_REPUBLIC\0"
302                                                                                                                         "VENEZUELA\0"
303                                                                                                                         "COLOMBIA\0"
304                                                                                                                         "PERU\0"
305                                                                                                                         "ARGENTINA\0"
306                                                                                                                         "ECUADOR\0"
307                                                                                                                         "CHILE\0"
308                                                                                                                         "URUGUAY\0"
309                                                                                                                         "PARAGUAY\0"
310                                                                                                                         "BOLIVIA\0"
311                                                                                                                         "EL_SALVADOR\0"
312                                                                                                                         "HONDURAS\0"
313                                                                                                                         "NICARAGUA\0"
314                                                                                                                         "PUERTO_RICO\0"
315         },
316         {
317                 LANG_SWAHILI,           "SWAHILI\0"
318         },
319         {
320                 MAKELANGID(LANG_SWEDISH, SUBLANG_SWEDISH),                      "SWEDISH\0"
321                                                                                                                         "FINLAND\0"
322         },
323         {
324                 LANG_SYRIAC,            "SYRIAC\0"
325         },
326         {
327                 LANG_TAMIL,                     "TAMIL\0"
328         },
329         {
330                 LANG_TATAR,                     "TATAR\0"
331         },
332         {
333                 LANG_TELUGU,            "TELUGU\0"
334         },
335         {
336                 LANG_THAI,                      "THAI\0"
337         },
338         {
339                 LANG_TURKISH,           "TURKISH\0"
340         },
341         {
342                 LANG_UKRAINIAN,         "UKRAINIAN\0"
343         },
344         {
345                 LANG_URDU,                      "URDU\0"                                                "PAKISTAN\0"
346                                                                                                                         "INDIA\0"
347         },
348         {
349                 LANG_UZBEK,                     "UZBEK\0"                                               "LATIN\0"
350                                                                                                                         "CYRILLIC\0"
351         },
352         {
353                 LANG_VIETNAMESE,        "VIETNAMESE\0"
354         },
355 };
356
357 /**
358  * @brief Get a language ID for given language + sublanguage.
359  * @param [in] lang Language name.
360  * @param [in] sublang Sub language name.
361  * @return Language ID.
362  */
363 LANGID LangFileInfo::LangId(const char *lang, const char *sublang)
364 {
365         // binary search the array for passed in lang
366         size_t lower = 0;
367         size_t upper = std::size(rg);
368         while (lower < upper)
369         {
370                 size_t match = (upper + lower) >> 1;
371                 int cmp = strcmp(rg[match].lang, lang);
372                 if (cmp >= 0)
373                         upper = match;
374                 if (cmp <= 0)
375                         lower = match + 1;
376         }
377         if (lower <= upper)
378                 return 0;
379         LANGID baseid = rg[upper].id;
380         // figure out sublang
381         if ((baseid & ~0x3ff) && *sublang == '\0')
382                 return baseid;
383         LANGID id = PRIMARYLANGID(baseid);
384         if (0 == strcmp(sublang, "DEFAULT"))
385                 return MAKELANGID(id, SUBLANG_DEFAULT);
386         const char *sub = rg[upper].lang;
387         do
388         {
389                 do
390                 {
391                         id += MAKELANGID(0, 1);
392                 } while (id == baseid);
393                 sub += strlen(sub) + 1;
394                 if (0 == strcmp(sublang, sub))
395                         return id;
396         } while (*sub);
397         return 0;
398 }
399
400 /**
401  * @brief A constructor taking a path to language file as parameter.
402  * @param [in] path Full path to the language file.
403  */
404 LangFileInfo::LangFileInfo(LPCTSTR path)
405 : id(0)
406 {
407         FILE *f;
408         if (_tfopen_s(&f, path, _T("r,ccs=utf-8")) == 0 && f)
409         {
410                 wchar_t buf[1024 + 1];
411                 while (fgetws(buf, static_cast<int>(std::size(buf)) - 1, f) != nullptr)
412                 {
413                         int i = 0;
414                         wcscat_s(buf, L"1");
415                         swscanf_s(buf, L"msgid \" LANG_ENGLISH , SUBLANG_ENGLISH_US \" %d", &i);
416                         if (i)
417                         {
418                                 if (fgetws(buf, static_cast<int>(std::size(buf)), f) != nullptr)
419                                 {
420                                         wchar_t *lang = wcsstr(buf, L"LANG_");
421                                         wchar_t *sublang = wcsstr(buf, L"SUBLANG_");
422                                         wchar_t *langNext = nullptr;
423                                         wchar_t *sublangNext = nullptr;
424                                         if (lang && sublang)
425                                         {
426                                                 wcstok_s(lang, L",\" \t\r\n", &langNext);
427                                                 wcstok_s(sublang, L",\" \t\r\n", &sublangNext);
428                                                 lang += std::size("LANG");
429                                                 sublang += std::size("SUBLANG");
430                                                 if (0 != wcscmp(sublang, L"DEFAULT"))
431                                                 {
432                                                         sublang = EatPrefix(sublang, lang);
433                                                         if (sublang && *sublang)
434                                                                 sublang = EatPrefix(sublang, L"_");
435                                                 }
436                                                 if (sublang)
437                                                 {
438                                                         USES_CONVERSION;
439                                                         id = LangId(W2A(lang), W2A(sublang));
440                                                 }
441                                         }
442                                 }
443                                 break;
444                         }
445                 }
446                 fclose(f);
447         }
448 }
449
450 std::wstring LangFileInfo::GetString(LCTYPE type) const
451 {
452         std::wstring s;
453         if (int cch = GetLocaleInfo(id, type, 0, 0))
454         {
455                 s.resize(cch - 1);
456                 GetLocaleInfo(id, type, &*s.begin(), cch);
457         }
458         return s;
459 }
460
461 static HANDLE NTAPI FindFile(HANDLE h, LPCTSTR path, WIN32_FIND_DATA *fd)
462 {
463         if (h == INVALID_HANDLE_VALUE)
464         {
465                 h = FindFirstFile(path, fd);
466         }
467         else if (fd->dwFileAttributes == INVALID_FILE_ATTRIBUTES || !FindNextFile(h, fd))
468         {
469                 FindClose(h);
470                 h = INVALID_HANDLE_VALUE;
471         }
472         return h;
473 }
474
475 /////////////////////////////////////////////////////////////////////////////
476 // CLanguageSelect dialog
477
478 CLanguageSelect::CLanguageSelect()
479         : m_langId(0)
480 {
481 }
482
483 /**
484  * @brief Remove prefix from the string.
485  * @param [in] text String from which to jump over prefix.
486  * @param [in] prefix Prefix string to jump over.
487  * @return String without the prefix.
488  * @note Function returns pointer to original string,
489  *  it does not allocate a new string.
490  */
491 static wchar_t *EatPrefix(wchar_t *text, const wchar_t *prefix)
492 {
493         if (size_t len = wcslen(prefix))
494                 if (_memicmp(text, prefix, len * sizeof(wchar_t)) == 0)
495                         return text + len;
496         return 0;
497 }
498
499 /**
500  * @brief Convert C style \\nnn, \\r, \\n, \\t etc into their indicated characters.
501  * @param [in,out] s String to convert.
502  */
503 static void unslash(std::wstring &s)
504 {
505         wchar_t *p = &*s.begin();
506         wchar_t *q = p;
507         wchar_t c = {};
508         do
509         {
510                 wchar_t *r = q + 1;
511                 switch (c = *q)
512                 {
513                 case '\\':
514                         switch (c = *r++)
515                         {
516                         case 'a':
517                                 c = '\a';
518                                 break;
519                         case 'b':
520                                 c = '\b';
521                                 break;
522                         case 'f':
523                                 c = '\f';
524                                 break;
525                         case 'n':
526                                 c = '\n';
527                                 break;
528                         case 'r':
529                                 c = '\r';
530                                 break;
531                         case 't':
532                                 c = '\t';
533                                 break;
534                         case 'v':
535                                 c = '\v';
536                                 break;
537                         case 'x':
538                                 *p = (wchar_t)wcstol(r, &q, 16);
539                                 break;
540                         default:
541                                 *p = (wchar_t)wcstol(r - 1, &q, 8);
542                                 break;
543                         }
544                         if (q >= r)
545                                 break;
546                         [[fallthrough]];
547                 default:
548                         *p = c;
549                         q = r;
550                 }
551                 ++p;
552         } while (c != '\0');
553         s.resize(p - 1 - &*s.begin());
554 }
555
556 /**
557  * @brief Load language.file
558  * @param [in] wLangId 
559  * @return `true` on success, `false` otherwise.
560  */
561 bool CLanguageSelect::LoadLanguageFile(LANGID wLangId, const std::wstring& sLanguagesFolder)
562 {
563         m_langId = wLangId;
564         m_map_msgid_to_msgstr.clear();
565
566         std::wstring strPath = GetFileName(wLangId, sLanguagesFolder);
567         if (strPath.empty())
568                 return false;
569
570         wchar_t buf[1024];
571         std::wstring *ps = nullptr;
572         std::wstring msgctxt;
573         std::wstring msgid;
574         FILE *f;
575         if (_tfopen_s(&f, strPath.c_str(), _T("r,ccs=UTF-8")) != 0)
576                 return false;
577         ps = nullptr;
578         std::wstring format;
579         std::wstring msgstr;
580         std::wstring directive;
581         auto addToMap = [&]() {
582                 ps = nullptr;
583                 if (!msgctxt.empty())
584                         unslash(msgctxt);
585                 if (!msgid.empty())
586                         unslash(msgid);
587                 if (msgstr.empty())
588                         msgstr = msgid;
589                 unslash(msgstr);
590                 if (!msgid.empty())
591                 {
592                         if (msgctxt.empty())
593                                 m_map_msgid_to_msgstr.insert_or_assign(msgid, msgstr);
594                         else
595                                 m_map_msgid_to_msgstr.insert_or_assign(L"\x01\"" + msgctxt + L"\"" + msgid, msgstr);
596                 }
597                 msgctxt.erase();
598                 msgid.erase();
599                 msgstr.erase();
600         };
601         while (fgetws(buf, static_cast<int>(std::size(buf)), f) != nullptr)
602         {
603                 if (wchar_t *p1 = EatPrefix(buf, L"#,"))
604                 {
605                         format = p1;
606                         format.erase(0, format.find_first_not_of(L" \t\r\n"));
607                         format.erase(format.find_last_not_of(L" \t\r\n") + 1);
608                 }
609                 else if (wchar_t *p2 = EatPrefix(buf, L"#."))
610                 {
611                         directive = p2;
612                         directive.erase(0, directive.find_first_not_of(L" \t\r\n"));
613                         directive.erase(directive.find_last_not_of(L" \t\r\n") + 1);
614                 }
615                 else if (EatPrefix(buf, L"msgctxt "))
616                 {
617                         ps = &msgctxt;
618                 }
619                 else if (EatPrefix(buf, L"msgid "))
620                 {
621                         ps = &msgid;
622                 }
623                 else if (EatPrefix(buf, L"msgstr "))
624                 {
625                         ps = &msgstr;
626                 }
627                 if (ps != nullptr)
628                 {
629                         wchar_t *p = wcschr(buf, '"');
630                         wchar_t *q = wcsrchr(buf, '"');
631                         if (std::wstring::size_type n = q - p)
632                         {
633                                 ps->append(p + 1, n - 1);
634                         }
635                         else
636                         {
637                                 addToMap();
638                         }
639                 }
640         }
641         if (ps != nullptr)
642                 addToMap();
643         fclose(f);
644
645         return true;
646 }
647
648 /**
649  * @brief Get a language file for the specified language ID.
650  * This function gets a language file name for the given language ID. Language
651  * files are currently named as [languagename].po.
652  * @param [in] wLangId Language ID.
653  * @return Language filename, or empty string if no file for language found.
654  */
655 std::wstring CLanguageSelect::GetFileName(LANGID wLangId, const std::wstring& sLanguagesFolder)
656 {
657         std::wstring filename;
658         std::wstring path = sLanguagesFolder;
659         if (!path.empty() && path.back() != '\\')
660                 path += '\\';
661         std::wstring pattern = path + L"*.po";
662         WIN32_FIND_DATA ff;
663         HANDLE h = INVALID_HANDLE_VALUE;
664         while ((h = FindFile(h, pattern.c_str(), &ff)) != INVALID_HANDLE_VALUE)
665         {
666                 filename = path + ff.cFileName;
667                 LangFileInfo lfi(filename.c_str());
668                 if (lfi.id == wLangId)
669                         ff.dwFileAttributes = INVALID_FILE_ATTRIBUTES; // terminate loop
670                 else
671                         filename.erase();
672         }
673         return filename;
674 }
675
676 /////////////////////////////////////////////////////////////////////////////
677 // CLanguageSelect commands
678
679 bool CLanguageSelect::TranslateString(const std::wstring& msgid, std::wstring &s) const
680 {
681         if (m_map_msgid_to_msgstr.find(msgid) != m_map_msgid_to_msgstr.end())
682         {
683                 s = m_map_msgid_to_msgstr.at(msgid);
684                 return true;
685         }
686         if (msgid.length() > 2 && msgid[0] == '\x01' && msgid[1] == '"')
687         {
688                 size_t pos = msgid.find('"', 2);
689                 if (pos != std::wstring::npos)
690                 {
691                         s = msgid.substr(pos + 1);
692                         return true;
693                 }
694         }
695         s = msgid;
696         return false;
697 }
698