OSDN Git Service

Update translations
[winmerge-jp/winmerge-jp.git] / Src / Common / 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 #include <locale.h>
10 #include "version.h"
11 #include "BCMenu.h"
12 #include "Environment.h"
13
14 // Escaped character constants in range 0x80-0xFF are interpreted in current codepage
15 // Using C locale gets us direct mapping to Unicode codepoints
16 #pragma setlocale("C")
17
18 #ifdef _DEBUG
19 #define new DEBUG_NEW
20 #undef THIS_FILE
21 static char THIS_FILE[] = __FILE__;
22 #endif
23
24 /** @brief Relative path to WinMerge executable for lang files. */
25 static const TCHAR szRelativePath[] = _T("\\Languages\\");
26
27 static char *EatPrefix(char *text, const char *prefix);
28 static void unslash(unsigned codepage, std::string &s);
29 static HANDLE NTAPI FindFile(HANDLE h, LPCTSTR path, WIN32_FIND_DATA *fd);
30
31 /**
32  * @brief A class holding information about language file.
33  */
34 class LangFileInfo
35 {
36 public:
37         LANGID id; /**< Language ID. */
38
39         static LANGID LangId(const char *lang, const char *sublang);
40         
41         /**
42          * A constructor taking a language id as parameter.
43          * @param [in] id Language ID to use.
44          */
45         LangFileInfo(LANGID id): id(id) { };
46         
47         LangFileInfo(LPCTSTR path);
48         String GetString(LCTYPE type) const;
49
50 private:
51         struct rg
52         {
53                 LANGID id;
54                 const char *lang;
55         };
56         static const struct rg rg[];
57 };
58
59 /**
60  * @brief An array holding language IDs and names.
61  */
62 const struct LangFileInfo::rg LangFileInfo::rg[] =
63 {
64         {
65                 LANG_AFRIKAANS,         "AFRIKAANS\0"
66         },
67         {
68                 LANG_ALBANIAN,          "ALBANIAN\0"
69         },
70         {
71                 LANG_ARABIC,            "ARABIC\0"                                              "SAUDI_ARABIA\0"
72                                                                                                                         "IRAQ\0"
73                                                                                                                         "EGYPT\0"
74                                                                                                                         "LIBYA\0"
75                                                                                                                         "ALGERIA\0"
76                                                                                                                         "MOROCCO\0"
77                                                                                                                         "TUNISIA\0"
78                                                                                                                         "OMAN\0"
79                                                                                                                         "YEMEN\0"
80                                                                                                                         "SYRIA\0"
81                                                                                                                         "JORDAN\0"
82                                                                                                                         "LEBANON\0"
83                                                                                                                         "KUWAIT\0"
84                                                                                                                         "UAE\0"
85                                                                                                                         "BAHRAIN\0"
86                                                                                                                         "QATAR\0"
87         },
88         {
89                 LANG_ARMENIAN,          "ARMENIAN\0"
90         },
91         {
92                 LANG_ASSAMESE,          "ASSAMESE\0"
93         },
94         {
95                 LANG_AZERI,                     "AZERI\0"                                               "LATIN\0"
96                                                                                                                         "CYRILLIC\0"
97         },
98         {
99                 LANG_BASQUE,            "BASQUE\0"
100         },
101         {
102                 LANG_BELARUSIAN,        "BELARUSIAN\0"
103         },
104         {
105                 LANG_BENGALI,           "BENGALI\0"
106         },
107         {
108                 LANG_BULGARIAN,         "BULGARIAN\0"
109         },
110         {
111                 LANG_CATALAN,           "CATALAN\0"
112         },
113         {
114                 LANG_CHINESE,           "CHINESE\0"                                             "TRADITIONAL\0"
115                                                                                                                         "SIMPLIFIED\0"
116                                                                                                                         "HONGKONG\0"
117                                                                                                                         "SINGAPORE\0"
118                                                                                                                         "MACAU\0"
119         },
120         {
121                 LANG_CROATIAN,          "CROATIAN\0"
122         },
123         {
124                 LANG_CZECH,                     "CZECH\0"
125         },
126         {
127                 LANG_DANISH,            "DANISH\0"
128         },
129         {
130                 LANG_DIVEHI,            "DIVEHI\0"
131         },
132         {
133                 MAKELANGID(LANG_DUTCH, SUBLANG_DUTCH),                          "DUTCH\0"
134                                                                                                                         "BELGIAN\0"
135         },
136         {
137                 LANG_ENGLISH,           "ENGLISH\0"                                             "US\0"
138                                                                                                                         "UK\0"
139                                                                                                                         "AUS\0"
140                                                                                                                         "CAN\0"
141                                                                                                                         "NZ\0"
142                                                                                                                         "EIRE\0"
143                                                                                                                         "SOUTH_AFRICA\0"
144                                                                                                                         "JAMAICA\0"
145                                                                                                                         "CARIBBEAN\0"
146                                                                                                                         "BELIZE\0"
147                                                                                                                         "TRINIDAD\0"
148                                                                                                                         "ZIMBABWE\0"
149                                                                                                                         "PHILIPPINES\0"
150         },
151         {
152                 LANG_ESTONIAN,          "ESTONIAN\0"
153         },
154         {
155                 LANG_FAEROESE,          "FAEROESE\0"
156         },
157         {
158                 LANG_FARSI,                     "FARSI\0"
159         },
160         {
161                 LANG_FINNISH,           "FINNISH\0"
162         },
163         {
164                 MAKELANGID(LANG_FRENCH, SUBLANG_FRENCH),                        "FRENCH\0"
165                                                                                                                         "BELGIAN\0"
166                                                                                                                         "CANADIAN\0"
167                                                                                                                         "SWISS\0"
168                                                                                                                         "LUXEMBOURG\0"
169                                                                                                                         "MONACO\0"
170         },
171         {
172                 LANG_GALICIAN,          "GALICIAN\0"
173         },
174         {
175                 LANG_GEORGIAN,          "GEORGIAN\0"
176         },
177         {
178                 MAKELANGID(LANG_GERMAN, SUBLANG_GERMAN),                        "GERMAN\0"
179                                                                                                                         "SWISS\0"
180                                                                                                                         "AUSTRIAN\0"
181                                                                                                                         "LUXEMBOURG\0"
182                                                                                                                         "LIECHTENSTEIN"
183         },
184         {
185                 LANG_GREEK,                     "GREEK\0"
186         },
187         {
188                 LANG_GUJARATI,          "GUJARATI\0"
189         },
190         {
191                 LANG_HEBREW,            "HEBREW\0"
192         },
193         {
194                 LANG_HINDI,                     "HINDI\0"
195         },
196         {
197                 LANG_HUNGARIAN,         "HUNGARIAN\0"
198         },
199         {
200                 LANG_ICELANDIC,         "ICELANDIC\0"
201         },
202         {
203                 LANG_INDONESIAN,        "INDONESIAN\0"
204         },
205         {
206                 MAKELANGID(LANG_ITALIAN, SUBLANG_ITALIAN),                      "ITALIAN\0"
207                                                                                                                         "SWISS\0"
208         },
209         {
210                 LANG_JAPANESE,          "JAPANESE\0"
211         },
212         {
213                 LANG_KANNADA,           "KANNADA\0"
214         },
215         {
216                 MAKELANGID(LANG_KASHMIRI, SUBLANG_DEFAULT),                     "KASHMIRI\0"
217                                                                                                                         "SASIA\0"
218         },
219         {
220                 LANG_KAZAK,                     "KAZAK\0"
221         },
222         {
223                 LANG_KONKANI,           "KONKANI\0"
224         },
225         {
226                 MAKELANGID(LANG_KOREAN, SUBLANG_KOREAN),                        "KOREAN\0"
227         },
228         {
229                 LANG_KYRGYZ,            "KYRGYZ\0"
230         },
231         {
232                 LANG_LATVIAN,           "LATVIAN\0"
233         },
234         {
235                 MAKELANGID(LANG_LITHUANIAN, SUBLANG_LITHUANIAN),        "LITHUANIAN\0"
236         },
237         {
238                 LANG_MACEDONIAN,        "MACEDONIAN\0"
239         },
240         {
241                 LANG_MALAY,                     "MALAY\0"                                               "MALAYSIA\0"
242                                                                                                                         "BRUNEI_DARUSSALAM\0"
243         },
244         {
245                 LANG_MALAYALAM,         "MALAYALAM\0"
246         },
247         {
248                 LANG_MANIPURI,          "MANIPURI\0"
249         },
250         {
251                 LANG_MARATHI,           "MARATHI\0"
252         },
253         {
254                 LANG_MONGOLIAN,         "MONGOLIAN\0"
255         },
256         {
257                 MAKELANGID(LANG_NEPALI, SUBLANG_DEFAULT),                       "NEPALI\0"
258                                                                                                                         "INDIA\0"
259         },
260         {
261                 LANG_NORWEGIAN,         "NORWEGIAN\0"                                   "BOKMAL\0"
262                                                                                                                         "NYNORSK\0"
263         },
264         {
265                 LANG_ORIYA,                     "ORIYA\0"
266         },
267         {
268                 LANG_POLISH,            "POLISH\0"
269         },
270         {
271                 MAKELANGID(LANG_PORTUGUESE, SUBLANG_PORTUGUESE),        "PORTUGUESE\0"
272                                                                                                                         "BRAZILIAN\0"
273         },
274         {
275                 LANG_PUNJABI,           "PUNJABI\0"
276         },
277         {
278                 LANG_ROMANIAN,          "ROMANIAN\0"
279         },
280         {
281                 LANG_RUSSIAN,           "RUSSIAN\0"
282         },
283         {
284                 LANG_SANSKRIT,          "SANSKRIT\0"
285         },
286         {
287                 MAKELANGID(LANG_SERBIAN, SUBLANG_DEFAULT),                      "SERBIAN\0"
288                                                                                                                         "LATIN\0"
289                                                                                                                         "CYRILLIC\0"
290         },
291         {
292                 LANG_SINDHI,            "SINDHI\0"
293         },
294         {
295                 LANG_SLOVAK,            "SLOVAK\0"
296         },
297         {
298                 LANG_SLOVENIAN,         "SLOVENIAN\0"
299         },
300         {
301                 MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH),                      "SPANISH\0"
302                                                                                                                         "MEXICAN\0"
303                                                                                                                         "MODERN\0"
304                                                                                                                         "GUATEMALA\0"
305                                                                                                                         "COSTA_RICA\0"
306                                                                                                                         "PANAMA\0"
307                                                                                                                         "DOMINICAN_REPUBLIC\0"
308                                                                                                                         "VENEZUELA\0"
309                                                                                                                         "COLOMBIA\0"
310                                                                                                                         "PERU\0"
311                                                                                                                         "ARGENTINA\0"
312                                                                                                                         "ECUADOR\0"
313                                                                                                                         "CHILE\0"
314                                                                                                                         "URUGUAY\0"
315                                                                                                                         "PARAGUAY\0"
316                                                                                                                         "BOLIVIA\0"
317                                                                                                                         "EL_SALVADOR\0"
318                                                                                                                         "HONDURAS\0"
319                                                                                                                         "NICARAGUA\0"
320                                                                                                                         "PUERTO_RICO\0"
321         },
322         {
323                 LANG_SWAHILI,           "SWAHILI\0"
324         },
325         {
326                 MAKELANGID(LANG_SWEDISH, SUBLANG_SWEDISH),                      "SWEDISH\0"
327                                                                                                                         "FINLAND\0"
328         },
329         {
330                 LANG_SYRIAC,            "SYRIAC\0"
331         },
332         {
333                 LANG_TAMIL,                     "TAMIL\0"
334         },
335         {
336                 LANG_TATAR,                     "TATAR\0"
337         },
338         {
339                 LANG_TELUGU,            "TELUGU\0"
340         },
341         {
342                 LANG_THAI,                      "THAI\0"
343         },
344         {
345                 LANG_TURKISH,           "TURKISH\0"
346         },
347         {
348                 LANG_UKRAINIAN,         "UKRAINIAN\0"
349         },
350         {
351                 LANG_URDU,                      "URDU\0"                                                "PAKISTAN\0"
352                                                                                                                         "INDIA\0"
353         },
354         {
355                 LANG_UZBEK,                     "UZBEK\0"                                               "LATIN\0"
356                                                                                                                         "CYRILLIC\0"
357         },
358         {
359                 LANG_VIETNAMESE,        "VIETNAMESE\0"
360         },
361 };
362
363 /**
364  * @brief Get a language ID for given language + sublanguage.
365  * @param [in] lang Language name.
366  * @param [in] sublang Sub language name.
367  * @return Language ID.
368  */
369 LANGID LangFileInfo::LangId(const char *lang, const char *sublang)
370 {
371         // binary search the array for passed in lang
372         size_t lower = 0;
373         size_t upper = countof(rg);
374         while (lower < upper)
375         {
376                 size_t match = (upper + lower) >> 1;
377                 int cmp = strcmp(rg[match].lang, lang);
378                 if (cmp >= 0)
379                         upper = match;
380                 if (cmp <= 0)
381                         lower = match + 1;
382         }
383         if (lower <= upper)
384                 return 0;
385         LANGID baseid = rg[upper].id;
386         // figure out sublang
387         if ((baseid & ~0x3ff) && *sublang == '\0')
388                 return baseid;
389         LANGID id = PRIMARYLANGID(baseid);
390         if (0 == strcmp(sublang, "DEFAULT"))
391                 return MAKELANGID(id, SUBLANG_DEFAULT);
392         const char *sub = rg[upper].lang;
393         do
394         {
395                 do
396                 {
397                         id += MAKELANGID(0, 1);
398                 } while (id == baseid);
399                 sub += strlen(sub) + 1;
400                 if (0 == strcmp(sublang, sub))
401                         return id;
402         } while (*sub);
403         return 0;
404 }
405
406 /**
407  * @brief A constructor taking a path to language file as parameter.
408  * @param [in] path Full path to the language file.
409  */
410 LangFileInfo::LangFileInfo(LPCTSTR path)
411 : id(0)
412 {
413         if (FILE *f = _tfopen(path, _T("r")))
414         {
415                 char buf[1024 + 1];
416                 while (fgets(buf, sizeof buf - 1, f))
417                 {
418                         int i = 0;
419                         strcat(buf, "1");
420                         sscanf(buf, "msgid \" LANG_ENGLISH , SUBLANG_ENGLISH_US \" %d", &i);
421                         if (i)
422                         {
423                                 if (fgets(buf, sizeof buf, f))
424                                 {
425                                         char *lang = strstr(buf, "LANG_");
426                                         char *sublang = strstr(buf, "SUBLANG_");
427                                         if (lang && sublang)
428                                         {
429                                                 strtok(lang, ",\" \t\r\n");
430                                                 strtok(sublang, ",\" \t\r\n");
431                                                 lang += sizeof "LANG";
432                                                 sublang += sizeof "SUBLANG";
433                                                 if (0 != strcmp(sublang, "DEFAULT"))
434                                                 {
435                                                         sublang = EatPrefix(sublang, lang);
436                                                         if (sublang && *sublang)
437                                                                 sublang = EatPrefix(sublang, "_");
438                                                 }
439                                                 if (sublang)
440                                                         id = LangId(lang, sublang);
441                                         }
442                                 }
443                                 break;
444                         }
445                 }
446                 fclose(f);
447         }
448 }
449
450 String LangFileInfo::GetString(LCTYPE type) const
451 {
452         String 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 /** @brief Default English language. */
479 const WORD wSourceLangId = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);
480
481 CLanguageSelect::CLanguageSelect()
482 : m_hCurrentDll(0)
483 , m_wCurLanguage(wSourceLangId)
484 {
485         SetThreadLocale(MAKELCID(m_wCurLanguage, SORT_DEFAULT));
486 }
487
488 /**
489  * @brief Remove prefix from the string.
490  * @param [in] text String from which to jump over prefix.
491  * @param [in] prefix Prefix string to jump over.
492  * @return String without the prefix.
493  * @note Function returns pointer to original string,
494  *  it does not allocate a new string.
495  */
496 static char *EatPrefix(char *text, const char *prefix)
497 {
498         if (size_t len = strlen(prefix))
499                 if (_memicmp(text, prefix, len) == 0)
500                         return text + len;
501         return 0;
502 }
503
504 /**
505  * @brief Convert C style \\nnn, \\r, \\n, \\t etc into their indicated characters.
506  * @param [in] codepage Codepage to use in conversion.
507  * @param [in,out] s String to convert.
508  */
509 static void unslash(unsigned codepage, std::string &s)
510 {
511         char *p = &*s.begin();
512         char *q = p;
513         char c;
514         do
515         {
516                 char *r = q + 1;
517                 switch (c = *q)
518                 {
519                 case '\\':
520                         switch (c = *r++)
521                         {
522                         case 'a':
523                                 c = '\a';
524                                 break;
525                         case 'b':
526                                 c = '\b';
527                                 break;
528                         case 'f':
529                                 c = '\f';
530                                 break;
531                         case 'n':
532                                 c = '\n';
533                                 break;
534                         case 'r':
535                                 c = '\r';
536                                 break;
537                         case 't':
538                                 c = '\t';
539                                 break;
540                         case 'v':
541                                 c = '\v';
542                                 break;
543                         case 'x':
544                                 *p = (char)strtol(r, &q, 16);
545                                 break;
546                         default:
547                                 *p = (char)strtol(r - 1, &q, 8);
548                                 break;
549                         }
550                         if (q >= r)
551                                 break;
552                         // fall through
553                 default:
554                         *p = c;
555                         if ((*p & 0x80) && IsDBCSLeadByteEx(codepage, *p))
556                                 *++p = *r++;
557                         q = r;
558                 }
559                 ++p;
560         } while (c != '\0');
561         s.resize(p - 1 - &*s.begin());
562 }
563
564 /**
565  * @brief Load language.file
566  * @param [in] wLangId 
567  * @return TRUE on success, FALSE otherwise.
568  */
569 BOOL CLanguageSelect::LoadLanguageFile(LANGID wLangId, BOOL bShowError)
570 {
571         String strPath = GetFileName(wLangId);
572         if (strPath.empty())
573                 return FALSE;
574
575         m_hCurrentDll = LoadLibrary(_T("MergeLang.dll"));
576         // There is no point in translating error messages about inoperational
577         // translation system, so go without string resources here.
578         if (m_hCurrentDll == 0)
579         {
580                 if (bShowError)
581                         AfxMessageBox(_T("Failed to load MergeLang.dll"), MB_ICONSTOP);
582                 return FALSE;
583         }
584         CVersionInfo viInstance = AfxGetInstanceHandle();
585         unsigned instanceVerMS = 0;
586         unsigned instanceVerLS = 0;
587         viInstance.GetFixedFileVersion(instanceVerMS, instanceVerLS);
588         CVersionInfo viResource = m_hCurrentDll;
589         unsigned resourceVerMS = 0;
590         unsigned resourceVerLS = 0;
591         viResource.GetFixedFileVersion(resourceVerMS, resourceVerLS);
592         if (instanceVerMS != resourceVerMS || instanceVerLS != resourceVerLS)
593         {
594                 FreeLibrary(m_hCurrentDll);
595                 m_hCurrentDll = 0;
596                 if (bShowError)
597                         AfxMessageBox(_T("MergeLang.dll version mismatch"), MB_ICONSTOP);
598                 return FALSE;
599         }
600         HRSRC mergepot = FindResource(m_hCurrentDll, _T("MERGEPOT"), RT_RCDATA);
601         if (mergepot == 0)
602         {
603                 if (bShowError)
604                         AfxMessageBox(_T("MergeLang.dll is invalid"), MB_ICONSTOP);
605                 return FALSE;
606         }
607         size_t size = SizeofResource(m_hCurrentDll, mergepot);
608         const char *data = (const char *)LoadResource(m_hCurrentDll, mergepot);
609         char buf[1024];
610         std::string *ps = 0;
611         std::string msgid;
612         std::vector<unsigned> lines;
613         int unresolved = 0;
614         int mismatched = 0;
615         while (const char *eol = (const char *)memchr(data, '\n', size))
616         {
617                 size_t len = eol - data;
618                 if (len >= sizeof buf)
619                 {
620                         ASSERT(FALSE);
621                         break;
622                 }
623                 memcpy(buf, data, len);
624                 buf[len++] = '\0';
625                 data += len;
626                 size -= len;
627                 if (char *p = EatPrefix(buf, "#:"))
628                 {
629                         if (char *q = strchr(p, ':'))
630                         {
631                                 int line = strtol(q + 1, &q, 10);
632                                 lines.push_back(line);
633                                 ++unresolved;
634                         }
635                 }
636                 else if (EatPrefix(buf, "msgid "))
637                 {
638                         ps = &msgid;
639                 }
640                 if (ps)
641                 {
642                         char *p = strchr(buf, '"');
643                         char *q = strrchr(buf, '"');
644                         if (std::string::size_type n = q - p)
645                         {
646                                 ps->append(p + 1, n - 1);
647                         }
648                         else
649                         {
650                                 ps = 0;
651                                 // avoid dereference of empty vector or last vector
652                                 if (lines.size() > 0)
653                                 {
654                                         for (unsigned *pline = &*lines.begin() ; pline <= &*(lines.end() - 1) ; ++pline)
655                                         {
656                                                 unsigned line = *pline;
657                                                 if (m_strarray.size() <= line)
658                                                         m_strarray.resize(line + 1);
659                                                 m_strarray[line] = msgid;
660                                         }
661                                 }
662                                 lines.clear();
663                                 msgid.erase();
664                         }
665                 }
666         }
667         FILE *f = _tfopen(strPath.c_str(), _T("r"));
668         if (f == 0)
669         {
670                 FreeLibrary(m_hCurrentDll);
671                 m_hCurrentDll = 0;
672                 if (bShowError)
673                 {
674                         String str = _T("Failed to load ") + strPath;
675                         AfxMessageBox(str.c_str(), MB_ICONSTOP);
676                 }
677                 return FALSE;
678         }
679         ps = 0;
680         msgid.erase();
681         lines.clear();
682         std::string format;
683         std::string msgstr;
684         std::string directive;
685         while (fgets(buf, sizeof buf, f))
686         {
687                 if (char *p = EatPrefix(buf, "#:"))
688                 {
689                         if (char *q = strchr(p, ':'))
690                         {
691                                 int line = strtol(q + 1, &q, 10);
692                                 lines.push_back(line);
693                                 --unresolved;
694                         }
695                 }
696                 else if (char *p = EatPrefix(buf, "#,"))
697                 {
698                         format = p;
699                         format.erase(0, format.find_first_not_of(" \t\r\n"));
700                         format.erase(format.find_last_not_of(" \t\r\n") + 1);
701                 }
702                 else if (char *p = EatPrefix(buf, "#."))
703                 {
704                         directive = p;
705                         directive.erase(0, directive.find_first_not_of(" \t\r\n"));
706                         directive.erase(directive.find_last_not_of(" \t\r\n") + 1);
707                 }
708                 else if (EatPrefix(buf, "msgid "))
709                 {
710                         ps = &msgid;
711                 }
712                 else if (EatPrefix(buf, "msgstr "))
713                 {
714                         ps = &msgstr;
715                 }
716                 if (ps)
717                 {
718                         char *p = strchr(buf, '"');
719                         char *q = strrchr(buf, '"');
720                         if (std::string::size_type n = q - p)
721                         {
722                                 ps->append(p + 1, n - 1);
723                         }
724                         else
725                         {
726                                 ps = 0;
727                                 if (msgstr.empty())
728                                         msgstr = msgid;
729                                 unslash(m_codepage, msgstr);
730                                 // avoid dereference of empty vector or last vector
731                                 if (lines.size()>0)
732                                 {
733                                         for (unsigned *pline = &*lines.begin() ; pline <= &*(lines.end() - 1) ; ++pline)
734                                         {
735                                                 unsigned line = *pline;
736                                                 if (m_strarray.size() <= line)
737                                                         m_strarray.resize(line + 1);
738                                                 if (m_strarray[line] == msgid)
739                                                         m_strarray[line] = msgstr;
740                                                 else
741                                                         ++mismatched;
742                                         }
743                                 }
744                                 lines.clear();
745                                 if (directive == "Codepage")
746                                 {
747                                         m_codepage = strtol(msgstr.c_str(), &p, 10);
748                                         directive.erase();
749                                 }
750                                 msgid.erase();
751                                 msgstr.erase();
752                         }
753                 }
754         }
755         fclose(f);
756         if (unresolved != 0 || mismatched != 0)
757         {
758                 FreeLibrary(m_hCurrentDll);
759                 m_hCurrentDll = 0;
760                 m_strarray.clear();
761                 m_codepage = 0;
762                 if (bShowError)
763                 {
764                         String str = _T("Unresolved or mismatched references detected when ")
765                                 _T("attempting to read translations from\n") + strPath;
766                         AfxMessageBox(str.c_str(), MB_ICONSTOP);
767                 }
768                 return FALSE;
769         }
770         return TRUE;
771 }
772
773 /**
774  * @brief Set UI language.
775  * @param [in] wLangId 
776  * @return TRUE on success, FALSE otherwise.
777  */
778 BOOL CLanguageSelect::SetLanguage(LANGID wLangId, BOOL bShowError)
779 {
780         if (wLangId == 0)
781                 return FALSE;
782         if (m_wCurLanguage == wLangId)
783                 return TRUE;
784         // reset the resource handle
785         AfxSetResourceHandle(AfxGetInstanceHandle());
786         // free the existing DLL
787         if (m_hCurrentDll)
788         {
789                 FreeLibrary(m_hCurrentDll);
790                 m_hCurrentDll = NULL;
791         }
792         m_strarray.clear();
793         m_codepage = 0;
794         if (wLangId != wSourceLangId)
795         {
796                 if (LoadLanguageFile(wLangId, bShowError))
797                         AfxSetResourceHandle(m_hCurrentDll);
798                 else
799                         wLangId = wSourceLangId;
800         }
801         m_wCurLanguage = wLangId;
802         SetThreadLocale(MAKELCID(m_wCurLanguage, SORT_DEFAULT));
803         return TRUE;
804 }
805
806 /**
807  * @brief Get a language file for the specified language ID.
808  * This function gets a language file name for the given language ID. Language
809  * files are currently named as [languagename].po.
810  * @param [in] wLangId Language ID.
811  * @return Language filename, or empty string if no file for language found.
812  */
813 String CLanguageSelect::GetFileName(LANGID wLangId) const
814 {
815         String filename;
816         String path = env_GetProgPath().append(szRelativePath);
817         String pattern = path + _T("*.po");
818         WIN32_FIND_DATA ff;
819         HANDLE h = INVALID_HANDLE_VALUE;
820         while ((h = FindFile(h, pattern.c_str(), &ff)) != INVALID_HANDLE_VALUE)
821         {
822                 filename = path + ff.cFileName;
823                 LangFileInfo lfi = filename.c_str();
824                 if (lfi.id == wLangId)
825                         ff.dwFileAttributes = INVALID_FILE_ATTRIBUTES; // terminate loop
826                 else
827                         filename.erase();
828         }
829         return filename;
830 }
831
832 /////////////////////////////////////////////////////////////////////////////
833 // CLanguageSelect commands
834
835 bool CLanguageSelect::TranslateString(size_t line, std::string &s) const
836 {
837         if (line > 0 && line < m_strarray.size())
838         {
839                 s = m_strarray[line];
840                 unsigned codepage = GetACP();
841                 if (m_codepage != codepage)
842                 {
843                         // Attempt to convert to UI codepage
844                         if (int len = static_cast<int>(s.length()))
845                         {
846                                 std::wstring ws;
847                                 ws.resize(len);
848                                 len = MultiByteToWideChar(m_codepage, 0, s.c_str(), -1, &*ws.begin(), len + 1);
849                                 if (len)
850                                 {
851                                         ws.resize(len - 1);
852                                         len = WideCharToMultiByte(codepage, 0, ws.c_str(), -1, 0, 0, 0, 0);
853                                         if (len)
854                                         {
855                                                 s.resize(len - 1);
856                                                 WideCharToMultiByte(codepage, 0, ws.c_str(), -1, &*s.begin(), len, 0, 0);
857                                         }
858                                 }
859                         }
860                 }
861                 return true;
862         }
863         return false;
864 }
865
866 bool CLanguageSelect::TranslateString(size_t line, std::wstring &ws) const
867 {
868         if (line > 0 && line < m_strarray.size())
869         {
870                 if (int len = static_cast<int>(m_strarray[line].length()))
871                 {
872                         ws.resize(len);
873                         const char *msgstr = m_strarray[line].c_str();
874                         len = MultiByteToWideChar(m_codepage, 0, msgstr, -1, &*ws.begin(), len + 1);
875                         ws.resize(len - 1);
876                         return true;
877                 }
878         }
879         return false;
880 }
881
882 void CLanguageSelect::SetIndicators(CStatusBar &sb, const UINT *rgid, int n) const
883 {
884         HGDIOBJ hf = (HGDIOBJ)sb.SendMessage(WM_GETFONT);
885         CClientDC dc(0);
886         if (hf)
887                 hf = dc.SelectObject(hf);
888         if (n)
889                 sb.SetIndicators(0, n);
890         else
891                 n = sb.m_nCount;
892         int cx = ::GetSystemMetrics(SM_CXSCREEN) / 4;   // default to 1/4 the screen width
893         UINT style = SBPS_STRETCH | SBPS_NOBORDERS;             // first pane is stretchy
894         for (int i = 0 ; i < n ; ++i)
895         {
896                 UINT id = rgid ? rgid[i] : sb.GetItemID(i);
897                 if (id >= ID_INDICATOR_EXT)
898                 {
899                         String text = LoadString(id);
900                         int cx = dc.GetTextExtent(text.c_str(), static_cast<int>(text.length())).cx;
901                         sb.SetPaneInfo(i, id, style | SBPS_DISABLED, cx);
902                         sb.SetPaneText(i, text.c_str(), FALSE);
903                 }
904                 else if (rgid)
905                 {
906                         sb.SetPaneInfo(i, 0, style, cx);
907                 }
908                 style = 0;
909         }
910         if (hf)
911                 hf = dc.SelectObject(hf);
912         // Send WM_SIZE to get pane rectangles right
913         RECT rect;
914         sb.GetClientRect(&rect);
915         sb.SendMessage(WM_SIZE, 0, MAKELPARAM(rect.right, rect.bottom));
916 }
917
918 void CLanguageSelect::TranslateMenu(HMENU h) const
919 {
920         int i = ::GetMenuItemCount(h);
921         while (i > 0)
922         {
923                 --i;
924                 MENUITEMINFO mii = {0};
925 #if(WINVER >= 0x0500)
926                 mii.cbSize = sizeof mii - sizeof HBITMAP;
927 #else
928                 mii.cbSize = sizeof mii;
929 #endif
930                 mii.fMask = MIIM_STATE|MIIM_ID|MIIM_SUBMENU|MIIM_DATA;
931                 ::GetMenuItemInfo(h, i, TRUE, &mii);
932                 if (mii.hSubMenu)
933                 {
934                         TranslateMenu(mii.hSubMenu);
935                         mii.wID = reinterpret_cast<UINT>(mii.hSubMenu);
936                 }
937                 if (BCMenuData *pItemData = reinterpret_cast<BCMenuData *>(mii.dwItemData))
938                 {
939                         if (LPCWSTR text = pItemData->GetWideString())
940                         {
941                                 unsigned line = 0;
942                                 swscanf(text, L"Merge.rc:%u", &line);
943                                 std::wstring s;
944                                 if (TranslateString(line, s))
945                                         pItemData->SetWideString(s.c_str());
946                         }
947                 }
948                 TCHAR text[80];
949                 if (::GetMenuString(h, i, text, countof(text), MF_BYPOSITION))
950                 {
951                         unsigned line = 0;
952                         _stscanf(text, _T("Merge.rc:%u"), &line);
953                         String s;
954                         if (TranslateString(line, s))
955                                 ::ModifyMenu(h, i, mii.fState | MF_BYPOSITION, mii.wID, s.c_str());
956                 }
957         }
958 }
959
960 void CLanguageSelect::TranslateDialog(HWND h) const
961 {
962         UINT gw = GW_CHILD;
963         do
964         {
965                 TCHAR text[80];
966                 ::GetWindowText(h, text, countof(text));
967                 unsigned line = 0;
968                 _stscanf(text, _T("Merge.rc:%u"), &line);
969                 String s;
970                 if (TranslateString(line, s))
971                         ::SetWindowText(h, s.c_str());
972                 h = ::GetWindow(h, gw);
973                 gw = GW_HWNDNEXT;
974         } while (h);
975 }
976
977 String CLanguageSelect::LoadString(UINT id) const
978 {
979         String s;
980         if (id)
981         {
982                 TCHAR text[1024];
983                 AfxLoadString(id, text, countof(text));
984                 unsigned line = 0;
985                 _stscanf(text, _T("Merge.rc:%u"), &line);
986                 if (!TranslateString(line, s))
987                         s = text;
988         }
989         return s;
990 }
991
992 std::wstring CLanguageSelect::LoadDialogCaption(LPCTSTR lpDialogTemplateID) const
993 {
994         std::wstring s;
995         if (HINSTANCE hInst = AfxFindResourceHandle(lpDialogTemplateID, RT_DIALOG))
996         {
997                 if (HRSRC hRsrc = FindResource(hInst, lpDialogTemplateID, RT_DIALOG))
998                 {
999                         if (LPCWSTR text = (LPCWSTR)LoadResource(hInst, hRsrc))
1000                         {
1001                                 // Skip DLGTEMPLATE or DLGTEMPLATEEX
1002                                 text += text[1] == 0xFFFF ? 13 : 9;
1003                                 // Skip menu name string or ordinal
1004                                 if (*text == (const WCHAR)-1)
1005                                         text += 2; // WCHARs
1006                                 else
1007                                         while (*text++);
1008                                 // Skip class name string or ordinal
1009                                 if (*text == (const WCHAR)-1)
1010                                         text += 2; // WCHARs
1011                                 else
1012                                         while (*text++);
1013                                 // Caption string is ahead
1014                                 unsigned line = 0;
1015                                 swscanf(text, L"Merge.rc:%u", &line);
1016                                 if (!TranslateString(line, s))
1017                                         s = text;
1018                         }
1019                 }
1020         }
1021         return s;
1022 }
1023
1024 /**
1025  * @brief Load languages available on disk, and display in list, and select current
1026  */
1027 std::vector<std::pair<LANGID, String> > CLanguageSelect::GetAvailableLanguages() const
1028 {
1029         std::vector<std::pair<LANGID, String> > list;
1030         String path = env_GetProgPath().append(szRelativePath);
1031         String pattern = path + _T("*.po");
1032         WIN32_FIND_DATA ff;
1033         HANDLE h = INVALID_HANDLE_VALUE;
1034         do
1035         {
1036                 LangFileInfo &lfi =
1037                         h == INVALID_HANDLE_VALUE
1038                 ?       LangFileInfo(wSourceLangId)
1039                 :       LangFileInfo((path + ff.cFileName).c_str());
1040                 String str;
1041                 str += lfi.GetString(LOCALE_SLANGUAGE);
1042                 str += _T(" - ");
1043                 str += lfi.GetString(LOCALE_SNATIVELANGNAME | LOCALE_USE_CP_ACP);
1044                 str += _T(" (");
1045                 str += lfi.GetString(LOCALE_SNATIVECTRYNAME | LOCALE_USE_CP_ACP);
1046                 str += _T(")");
1047                 str += _T(" - ");
1048                 str += lfi.GetString(LOCALE_SENGLANGUAGE);
1049                 str += _T(" (");
1050                 str += lfi.GetString(LOCALE_SENGCOUNTRY);
1051                 str += _T(")");
1052                 list.emplace_back(lfi.id, str);
1053         } while ((h = FindFile(h, pattern.c_str(), &ff)) != INVALID_HANDLE_VALUE);
1054         return list;
1055 }
1056
1057 /**
1058  * @brief Find DLL entry in lang_map for language for specified locale
1059  */
1060 static WORD GetLangFromLocale(LCID lcid)
1061 {
1062         TCHAR buff[8] = {0};
1063         WORD langID = 0;
1064         if (GetLocaleInfo(lcid, LOCALE_IDEFAULTLANGUAGE, buff, countof(buff)))
1065                 _stscanf(buff, _T("%4hx"), &langID);
1066         return langID;
1067 }
1068
1069 void CLanguageSelect::InitializeLanguage(WORD langID)
1070 {
1071         ASSERT(LangFileInfo::LangId("GERMAN", "") == MAKELANGID(LANG_GERMAN, SUBLANG_GERMAN));
1072         ASSERT(LangFileInfo::LangId("GERMAN", "DEFAULT") == MAKELANGID(LANG_GERMAN, SUBLANG_DEFAULT));
1073         ASSERT(LangFileInfo::LangId("GERMAN", "SWISS") == MAKELANGID(LANG_GERMAN, SUBLANG_GERMAN_SWISS));
1074         ASSERT(LangFileInfo::LangId("PORTUGUESE", "") == MAKELANGID(LANG_PORTUGUESE, SUBLANG_PORTUGUESE));
1075         ASSERT(LangFileInfo::LangId("NORWEGIAN", "BOKMAL") == MAKELANGID(LANG_NORWEGIAN, SUBLANG_NORWEGIAN_BOKMAL));
1076         ASSERT(LangFileInfo::LangId("NORWEGIAN", "NYNORSK") == MAKELANGID(LANG_NORWEGIAN, SUBLANG_NORWEGIAN_NYNORSK));
1077
1078         //TRACE(_T("%hs\n"), LangFileInfo::FileName(MAKELANGID(LANG_NORWEGIAN, SUBLANG_NORWEGIAN_BOKMAL)).c_str());
1079         //TRACE(_T("%hs\n"), LangFileInfo::FileName(MAKELANGID(LANG_PORTUGUESE, SUBLANG_PORTUGUESE)).c_str());
1080         //TRACE(_T("%hs\n"), LangFileInfo::FileName(MAKELANGID(LANG_GERMAN, SUBLANG_DEFAULT)).c_str());
1081
1082         if (langID)
1083         {
1084                 // User has set a language override
1085                 SetLanguage(langID);
1086                 return;
1087         }
1088         // User has not specified a language
1089         // so look in thread locale, user locale, and then system locale for
1090         // a language that WinMerge supports
1091         WORD Lang1 = GetLangFromLocale(GetThreadLocale());
1092         if (SetLanguage(Lang1))
1093                 return;
1094         WORD Lang2 = GetLangFromLocale(LOCALE_USER_DEFAULT);
1095         if (Lang2 != Lang1 && SetLanguage(Lang2))
1096                 return;
1097         WORD Lang3 = GetLangFromLocale(LOCALE_SYSTEM_DEFAULT);
1098         if (Lang3 != Lang2 && Lang3 != Lang1 && SetLanguage(Lang3))
1099                 return;
1100 }