OSDN Git Service

Merge with stable
[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 // ID line follows -- this is updated by SVN
7 // $Id: LanguageSelect.cpp 6499 2009-02-25 13:31:52Z kimmov $
8
9 #include "StdAfx.h"
10 #include "LanguageSelect.h"
11 #include <locale.h>
12 #include <sstream>
13 #include "OptionsDef.h"
14 #include "OptionsMgr.h"
15 #include "Merge.h"
16 #include "version.h"
17 #include "resource.h"
18 #include "BCMenu.h"
19 #include "MainFrm.h"
20 #include "OpenFrm.h"
21 #include "ChildFrm.h"
22 #include "DirFrame.h"
23 #include "paths.h"
24 #include "Environment.h"
25 #include "unicoder.h"
26
27 // Escaped character constants in range 0x80-0xFF are interpreted in current codepage
28 // Using C locale gets us direct mapping to Unicode codepoints
29 #pragma setlocale("C")
30
31 #ifdef _DEBUG
32 #define new DEBUG_NEW
33 #undef THIS_FILE
34 static char THIS_FILE[] = __FILE__;
35 #endif
36
37 /** @brief Relative path to WinMerge executable for lang files. */
38 static const TCHAR szRelativePath[] = _T("Languages");
39
40 static char *EatPrefix(char *text, const char *prefix);
41 static void unslash(unsigned codepage, std::string &s);
42 static HANDLE NTAPI FindFile(HANDLE h, LPCTSTR path, WIN32_FIND_DATA *fd);
43
44 /**
45  * @brief A class holding information about language file.
46  */
47 class LangFileInfo
48 {
49 public:
50         LANGID id; /**< Language ID. */
51
52         static LANGID LangId(const char *lang, const char *sublang);
53         
54         /**
55          * A constructor taking a language id as parameter.
56          * @param [in] id Language ID to use.
57          */
58         LangFileInfo(LANGID id): id(id) { };
59         
60         LangFileInfo(LPCTSTR path);
61         String GetString(LCTYPE type) const;
62
63 private:
64         struct rg
65         {
66                 LANGID id;
67                 const char *lang;
68         };
69         static const struct rg rg[];
70 };
71
72 /**
73  * @brief An array holding language IDs and names.
74  */
75 const struct LangFileInfo::rg LangFileInfo::rg[] =
76 {
77         {
78                 LANG_AFRIKAANS,         "AFRIKAANS\0"
79         },
80         {
81                 LANG_ALBANIAN,          "ALBANIAN\0"
82         },
83         {
84                 LANG_ARABIC,            "ARABIC\0"                                              "SAUDI_ARABIA\0"
85                                                                                                                         "IRAQ\0"
86                                                                                                                         "EGYPT\0"
87                                                                                                                         "LIBYA\0"
88                                                                                                                         "ALGERIA\0"
89                                                                                                                         "MOROCCO\0"
90                                                                                                                         "TUNISIA\0"
91                                                                                                                         "OMAN\0"
92                                                                                                                         "YEMEN\0"
93                                                                                                                         "SYRIA\0"
94                                                                                                                         "JORDAN\0"
95                                                                                                                         "LEBANON\0"
96                                                                                                                         "KUWAIT\0"
97                                                                                                                         "UAE\0"
98                                                                                                                         "BAHRAIN\0"
99                                                                                                                         "QATAR\0"
100         },
101         {
102                 LANG_ARMENIAN,          "ARMENIAN\0"
103         },
104         {
105                 LANG_ASSAMESE,          "ASSAMESE\0"
106         },
107         {
108                 LANG_AZERI,                     "AZERI\0"                                               "LATIN\0"
109                                                                                                                         "CYRILLIC\0"
110         },
111         {
112                 LANG_BASQUE,            "BASQUE\0"
113         },
114         {
115                 LANG_BELARUSIAN,        "BELARUSIAN\0"
116         },
117         {
118                 LANG_BENGALI,           "BENGALI\0"
119         },
120         {
121                 LANG_BULGARIAN,         "BULGARIAN\0"
122         },
123         {
124                 LANG_CATALAN,           "CATALAN\0"
125         },
126         {
127                 LANG_CHINESE,           "CHINESE\0"                                             "TRADITIONAL\0"
128                                                                                                                         "SIMPLIFIED\0"
129                                                                                                                         "HONGKONG\0"
130                                                                                                                         "SINGAPORE\0"
131                                                                                                                         "MACAU\0"
132         },
133         {
134                 LANG_CROATIAN,          "CROATIAN\0"
135         },
136         {
137                 LANG_CZECH,                     "CZECH\0"
138         },
139         {
140                 LANG_DANISH,            "DANISH\0"
141         },
142         {
143                 LANG_DIVEHI,            "DIVEHI\0"
144         },
145         {
146                 MAKELANGID(LANG_DUTCH, SUBLANG_DUTCH),                          "DUTCH\0"
147                                                                                                                         "BELGIAN\0"
148         },
149         {
150                 LANG_ENGLISH,           "ENGLISH\0"                                             "US\0"
151                                                                                                                         "UK\0"
152                                                                                                                         "AUS\0"
153                                                                                                                         "CAN\0"
154                                                                                                                         "NZ\0"
155                                                                                                                         "EIRE\0"
156                                                                                                                         "SOUTH_AFRICA\0"
157                                                                                                                         "JAMAICA\0"
158                                                                                                                         "CARIBBEAN\0"
159                                                                                                                         "BELIZE\0"
160                                                                                                                         "TRINIDAD\0"
161                                                                                                                         "ZIMBABWE\0"
162                                                                                                                         "PHILIPPINES\0"
163         },
164         {
165                 LANG_ESTONIAN,          "ESTONIAN\0"
166         },
167         {
168                 LANG_FAEROESE,          "FAEROESE\0"
169         },
170         {
171                 LANG_FARSI,                     "FARSI\0"
172         },
173         {
174                 LANG_FINNISH,           "FINNISH\0"
175         },
176         {
177                 MAKELANGID(LANG_FRENCH, SUBLANG_FRENCH),                        "FRENCH\0"
178                                                                                                                         "BELGIAN\0"
179                                                                                                                         "CANADIAN\0"
180                                                                                                                         "SWISS\0"
181                                                                                                                         "LUXEMBOURG\0"
182                                                                                                                         "MONACO\0"
183         },
184         {
185                 LANG_GALICIAN,          "GALICIAN\0"
186         },
187         {
188                 LANG_GEORGIAN,          "GEORGIAN\0"
189         },
190         {
191                 MAKELANGID(LANG_GERMAN, SUBLANG_GERMAN),                        "GERMAN\0"
192                                                                                                                         "SWISS\0"
193                                                                                                                         "AUSTRIAN\0"
194                                                                                                                         "LUXEMBOURG\0"
195                                                                                                                         "LIECHTENSTEIN"
196         },
197         {
198                 LANG_GREEK,                     "GREEK\0"
199         },
200         {
201                 LANG_GUJARATI,          "GUJARATI\0"
202         },
203         {
204                 LANG_HEBREW,            "HEBREW\0"
205         },
206         {
207                 LANG_HINDI,                     "HINDI\0"
208         },
209         {
210                 LANG_HUNGARIAN,         "HUNGARIAN\0"
211         },
212         {
213                 LANG_ICELANDIC,         "ICELANDIC\0"
214         },
215         {
216                 LANG_INDONESIAN,        "INDONESIAN\0"
217         },
218         {
219                 MAKELANGID(LANG_ITALIAN, SUBLANG_ITALIAN),                      "ITALIAN\0"
220                                                                                                                         "SWISS\0"
221         },
222         {
223                 LANG_JAPANESE,          "JAPANESE\0"
224         },
225         {
226                 LANG_KANNADA,           "KANNADA\0"
227         },
228         {
229                 MAKELANGID(LANG_KASHMIRI, SUBLANG_DEFAULT),                     "KASHMIRI\0"
230                                                                                                                         "SASIA\0"
231         },
232         {
233                 LANG_KAZAK,                     "KAZAK\0"
234         },
235         {
236                 LANG_KONKANI,           "KONKANI\0"
237         },
238         {
239                 MAKELANGID(LANG_KOREAN, SUBLANG_KOREAN),                        "KOREAN\0"
240         },
241         {
242                 LANG_KYRGYZ,            "KYRGYZ\0"
243         },
244         {
245                 LANG_LATVIAN,           "LATVIAN\0"
246         },
247         {
248                 MAKELANGID(LANG_LITHUANIAN, SUBLANG_LITHUANIAN),        "LITHUANIAN\0"
249         },
250         {
251                 LANG_MACEDONIAN,        "MACEDONIAN\0"
252         },
253         {
254                 LANG_MALAY,                     "MALAY\0"                                               "MALAYSIA\0"
255                                                                                                                         "BRUNEI_DARUSSALAM\0"
256         },
257         {
258                 LANG_MALAYALAM,         "MALAYALAM\0"
259         },
260         {
261                 LANG_MANIPURI,          "MANIPURI\0"
262         },
263         {
264                 LANG_MARATHI,           "MARATHI\0"
265         },
266         {
267                 LANG_MONGOLIAN,         "MONGOLIAN\0"
268         },
269         {
270                 MAKELANGID(LANG_NEPALI, SUBLANG_DEFAULT),                       "NEPALI\0"
271                                                                                                                         "INDIA\0"
272         },
273         {
274                 LANG_NORWEGIAN,         "NORWEGIAN\0"                                   "BOKMAL\0"
275                                                                                                                         "NYNORSK\0"
276         },
277         {
278                 LANG_ORIYA,                     "ORIYA\0"
279         },
280         {
281                 LANG_POLISH,            "POLISH\0"
282         },
283         {
284                 MAKELANGID(LANG_PORTUGUESE, SUBLANG_PORTUGUESE),        "PORTUGUESE\0"
285                                                                                                                         "BRAZILIAN\0"
286         },
287         {
288                 LANG_PUNJABI,           "PUNJABI\0"
289         },
290         {
291                 LANG_ROMANIAN,          "ROMANIAN\0"
292         },
293         {
294                 LANG_RUSSIAN,           "RUSSIAN\0"
295         },
296         {
297                 LANG_SANSKRIT,          "SANSKRIT\0"
298         },
299         {
300                 MAKELANGID(LANG_SERBIAN, SUBLANG_DEFAULT),                      "SERBIAN\0"
301                                                                                                                         "LATIN\0"
302                                                                                                                         "CYRILLIC\0"
303         },
304         {
305                 LANG_SINDHI,            "SINDHI\0"
306         },
307         {
308                 LANG_SLOVAK,            "SLOVAK\0"
309         },
310         {
311                 LANG_SLOVENIAN,         "SLOVENIAN\0"
312         },
313         {
314                 MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH),                      "SPANISH\0"
315                                                                                                                         "MEXICAN\0"
316                                                                                                                         "MODERN\0"
317                                                                                                                         "GUATEMALA\0"
318                                                                                                                         "COSTA_RICA\0"
319                                                                                                                         "PANAMA\0"
320                                                                                                                         "DOMINICAN_REPUBLIC\0"
321                                                                                                                         "VENEZUELA\0"
322                                                                                                                         "COLOMBIA\0"
323                                                                                                                         "PERU\0"
324                                                                                                                         "ARGENTINA\0"
325                                                                                                                         "ECUADOR\0"
326                                                                                                                         "CHILE\0"
327                                                                                                                         "URUGUAY\0"
328                                                                                                                         "PARAGUAY\0"
329                                                                                                                         "BOLIVIA\0"
330                                                                                                                         "EL_SALVADOR\0"
331                                                                                                                         "HONDURAS\0"
332                                                                                                                         "NICARAGUA\0"
333                                                                                                                         "PUERTO_RICO\0"
334         },
335         {
336                 LANG_SWAHILI,           "SWAHILI\0"
337         },
338         {
339                 MAKELANGID(LANG_SWEDISH, SUBLANG_SWEDISH),                      "SWEDISH\0"
340                                                                                                                         "FINLAND\0"
341         },
342         {
343                 LANG_SYRIAC,            "SYRIAC\0"
344         },
345         {
346                 LANG_TAMIL,                     "TAMIL\0"
347         },
348         {
349                 LANG_TATAR,                     "TATAR\0"
350         },
351         {
352                 LANG_TELUGU,            "TELUGU\0"
353         },
354         {
355                 LANG_THAI,                      "THAI\0"
356         },
357         {
358                 LANG_TURKISH,           "TURKISH\0"
359         },
360         {
361                 LANG_UKRAINIAN,         "UKRAINIAN\0"
362         },
363         {
364                 LANG_URDU,                      "URDU\0"                                                "PAKISTAN\0"
365                                                                                                                         "INDIA\0"
366         },
367         {
368                 LANG_UZBEK,                     "UZBEK\0"                                               "LATIN\0"
369                                                                                                                         "CYRILLIC\0"
370         },
371         {
372                 LANG_VIETNAMESE,        "VIETNAMESE\0"
373         },
374 };
375
376 /**
377  * @brief Get a language ID for given language + sublanguage.
378  * @param [in] lang Language name.
379  * @param [in] sublang Sub language name.
380  * @return Language ID.
381  */
382 LANGID LangFileInfo::LangId(const char *lang, const char *sublang)
383 {
384         // binary search the array for passed in lang
385         size_t lower = 0;
386         size_t upper = countof(rg);
387         while (lower < upper)
388         {
389                 size_t match = (upper + lower) >> 1;
390                 int cmp = strcmp(rg[match].lang, lang);
391                 if (cmp >= 0)
392                         upper = match;
393                 if (cmp <= 0)
394                         lower = match + 1;
395         }
396         if (lower <= upper)
397                 return 0;
398         LANGID baseid = rg[upper].id;
399         // figure out sublang
400         if ((baseid & ~0x3ff) && *sublang == '\0')
401                 return baseid;
402         LANGID id = PRIMARYLANGID(baseid);
403         if (0 == strcmp(sublang, "DEFAULT"))
404                 return MAKELANGID(id, SUBLANG_DEFAULT);
405         const char *sub = rg[upper].lang;
406         do
407         {
408                 do
409                 {
410                         id += MAKELANGID(0, 1);
411                 } while (id == baseid);
412                 sub += strlen(sub) + 1;
413                 if (0 == strcmp(sublang, sub))
414                         return id;
415         } while (*sub);
416         return 0;
417 }
418
419 /**
420  * @brief A constructor taking a path to language file as parameter.
421  * @param [in] path Full path to the language file.
422  */
423 LangFileInfo::LangFileInfo(LPCTSTR path)
424 : id(0)
425 {
426         if (FILE *f = _tfopen(path, _T("r")))
427         {
428                 char buf[1024 + 1];
429                 while (fgets(buf, sizeof buf - 1, f))
430                 {
431                         int i = 0;
432                         strcat(buf, "1");
433                         sscanf(buf, "msgid \" LANG_ENGLISH , SUBLANG_ENGLISH_US \" %d", &i);
434                         if (i)
435                         {
436                                 if (fgets(buf, sizeof buf, f))
437                                 {
438                                         char *lang = strstr(buf, "LANG_");
439                                         char *sublang = strstr(buf, "SUBLANG_");
440                                         if (lang && sublang)
441                                         {
442                                                 strtok(lang, ",\" \t\r\n");
443                                                 strtok(sublang, ",\" \t\r\n");
444                                                 lang += sizeof "LANG";
445                                                 sublang += sizeof "SUBLANG";
446                                                 if (0 != strcmp(sublang, "DEFAULT"))
447                                                 {
448                                                         sublang = EatPrefix(sublang, lang);
449                                                         if (sublang && *sublang)
450                                                                 sublang = EatPrefix(sublang, "_");
451                                                 }
452                                                 if (sublang)
453                                                         id = LangId(lang, sublang);
454                                         }
455                                 }
456                                 break;
457                         }
458                 }
459                 fclose(f);
460         }
461 }
462
463 String LangFileInfo::GetString(LCTYPE type) const
464 {
465         String s;
466         if (int cch = GetLocaleInfo(id, type, 0, 0))
467         {
468                 s.resize(cch - 1);
469                 GetLocaleInfo(id, type, &*s.begin(), cch);
470         }
471         return s;
472 }
473
474 static HANDLE NTAPI FindFile(HANDLE h, LPCTSTR path, WIN32_FIND_DATA *fd)
475 {
476         if (h == INVALID_HANDLE_VALUE)
477         {
478                 h = FindFirstFile(path, fd);
479         }
480         else if (fd->dwFileAttributes == INVALID_FILE_ATTRIBUTES || !FindNextFile(h, fd))
481         {
482                 FindClose(h);
483                 h = INVALID_HANDLE_VALUE;
484         }
485         return h;
486 }
487
488 /////////////////////////////////////////////////////////////////////////////
489 // CLanguageSelect dialog
490
491 /** @brief Default English language. */
492 const WORD wSourceLangId = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);
493
494 CLanguageSelect::CLanguageSelect(UINT idMainMenu, UINT idDocMenu, BOOL bReloadMenu /*=TRUE*/, BOOL bUpdateTitle /*=TRUE*/, CWnd* pParent /*=NULL*/)
495 : CDialog(CLanguageSelect::IDD, pParent)
496 , m_hCurrentDll(0)
497 , m_wCurLanguage(wSourceLangId)
498 , m_idMainMenu(idMainMenu)
499 , m_idDocMenu(idDocMenu)
500 , m_hModule(NULL)
501 , m_bReloadMenu(bReloadMenu)
502 , m_bUpdateTitle(bUpdateTitle)
503 {
504         SetThreadLocale(MAKELCID(m_wCurLanguage, SORT_DEFAULT));
505 }
506
507 void CLanguageSelect::DoDataExchange(CDataExchange* pDX)
508 {
509         CDialog::DoDataExchange(pDX);
510         //{{AFX_DATA_MAP(CLanguageSelect)
511         DDX_Control(pDX, IDC_LANGUAGE_LIST, m_ctlLangList);
512         //}}AFX_DATA_MAP
513 }
514
515 BEGIN_MESSAGE_MAP(CLanguageSelect, CDialog)
516 //{{AFX_MSG_MAP(CLanguageSelect)
517         ON_LBN_DBLCLK(IDC_LANGUAGE_LIST, OnOK)
518 //}}AFX_MSG_MAP
519 END_MESSAGE_MAP()
520
521 /**
522  * @brief Remove prefix from the string.
523  * @param [in] text String from which to jump over prefix.
524  * @param [in] prefix Prefix string to jump over.
525  * @return String without the prefix.
526  * @note Function returns pointer to original string,
527  *  it does not allocate a new string.
528  */
529 static char *EatPrefix(char *text, const char *prefix)
530 {
531         if (size_t len = strlen(prefix))
532                 if (_memicmp(text, prefix, len) == 0)
533                         return text + len;
534         return 0;
535 }
536
537 /**
538  * @brief Convert C style \\nnn, \\r, \\n, \\t etc into their indicated characters.
539  * @param [in] codepage Codepage to use in conversion.
540  * @param [in,out] s String to convert.
541  */
542 static void unslash(unsigned codepage, std::string &s)
543 {
544         char *p = &*s.begin();
545         char *q = p;
546         char c;
547         do
548         {
549                 char *r = q + 1;
550                 switch (c = *q)
551                 {
552                 case '\\':
553                         switch (c = *r++)
554                         {
555                         case 'a':
556                                 c = '\a';
557                                 break;
558                         case 'b':
559                                 c = '\b';
560                                 break;
561                         case 'f':
562                                 c = '\f';
563                                 break;
564                         case 'n':
565                                 c = '\n';
566                                 break;
567                         case 'r':
568                                 c = '\r';
569                                 break;
570                         case 't':
571                                 c = '\t';
572                                 break;
573                         case 'v':
574                                 c = '\v';
575                                 break;
576                         case 'x':
577                                 *p = (char)strtol(r, &q, 16);
578                                 break;
579                         default:
580                                 *p = (char)strtol(r - 1, &q, 8);
581                                 break;
582                         }
583                         if (q >= r)
584                                 break;
585                         // fall through
586                 default:
587                         *p = c;
588                         if ((*p & 0x80) && IsDBCSLeadByteEx(codepage, *p))
589                                 *++p = *r++;
590                         q = r;
591                 }
592                 ++p;
593         } while (c != '\0');
594         s.resize(p - 1 - &*s.begin());
595 }
596
597 /**
598  * @brief Load language.file
599  * @param [in] wLangId 
600  * @return TRUE on success, FALSE otherwise.
601  */
602 BOOL CLanguageSelect::LoadLanguageFile(LANGID wLangId)
603 {
604         String strPath = GetFileName(wLangId);
605         if (strPath.empty())
606                 return FALSE;
607
608         m_hCurrentDll = LoadLibrary(_T("MergeLang.dll"));
609         // There is no point in translating error messages about inoperational
610         // translation system, so go without string resources here.
611         if (m_hCurrentDll == 0)
612         {
613                 if (m_hWnd)
614                         AfxMessageBox(_T("Failed to load MergeLang.dll"), MB_ICONSTOP);
615                 return FALSE;
616         }
617         CVersionInfo viInstance = AfxGetInstanceHandle();
618         unsigned instanceVerMS = 0;
619         unsigned instanceVerLS = 0;
620         viInstance.GetFixedFileVersion(instanceVerMS, instanceVerLS);
621         CVersionInfo viResource = m_hCurrentDll;
622         unsigned resourceVerMS = 0;
623         unsigned resourceVerLS = 0;
624         viResource.GetFixedFileVersion(resourceVerMS, resourceVerLS);
625         if (instanceVerMS != resourceVerMS || instanceVerLS != resourceVerLS)
626         {
627                 FreeLibrary(m_hCurrentDll);
628                 m_hCurrentDll = 0;
629                 if (m_hWnd)
630                         AfxMessageBox(_T("MergeLang.dll version mismatch"), MB_ICONSTOP);
631                 return FALSE;
632         }
633         HRSRC mergepot = FindResource(m_hCurrentDll, _T("MERGEPOT"), RT_RCDATA);
634         if (mergepot == 0)
635         {
636                 if (m_hWnd)
637                         AfxMessageBox(_T("MergeLang.dll is invalid"), MB_ICONSTOP);
638                 return FALSE;
639         }
640         size_t size = SizeofResource(m_hCurrentDll, mergepot);
641         const char *data = (const char *)LoadResource(m_hCurrentDll, mergepot);
642         char buf[1024];
643         std::string *ps = 0;
644         std::string msgid;
645         std::vector<unsigned> lines;
646         int unresolved = 0;
647         int mismatched = 0;
648         while (const char *eol = (const char *)memchr(data, '\n', size))
649         {
650                 size_t len = eol - data;
651                 if (len >= sizeof buf)
652                 {
653                         ASSERT(FALSE);
654                         break;
655                 }
656                 memcpy(buf, data, len);
657                 buf[len++] = '\0';
658                 data += len;
659                 size -= len;
660                 if (char *p = EatPrefix(buf, "#:"))
661                 {
662                         if (char *q = strchr(p, ':'))
663                         {
664                                 int line = strtol(q + 1, &q, 10);
665                                 lines.push_back(line);
666                                 ++unresolved;
667                         }
668                 }
669                 else if (EatPrefix(buf, "msgid "))
670                 {
671                         ps = &msgid;
672                 }
673                 if (ps)
674                 {
675                         char *p = strchr(buf, '"');
676                         char *q = strrchr(buf, '"');
677                         if (std::string::size_type n = q - p)
678                         {
679                                 ps->append(p + 1, n - 1);
680                         }
681                         else
682                         {
683                                 ps = 0;
684                                 // avoid dereference of empty vector or last vector
685                                 if (lines.size() > 0)
686                                 {
687                                         unslash(0, msgid);
688                                         m_map_lineno.insert(std::make_pair(msgid, lines[0]));
689                                         for (unsigned *pline = &*lines.begin() ; pline <= &*(lines.end() - 1) ; ++pline)
690                                         {
691                                                 unsigned line = *pline;
692                                                 if (m_strarray.size() <= line)
693                                                         m_strarray.resize(line + 1);
694                                                 m_strarray[line] = msgid;
695                                         }
696                                 }
697                                 lines.clear();
698                                 msgid.erase();
699                         }
700                 }
701         }
702         FILE *f = _tfopen(strPath.c_str(), _T("r"));
703         if (f == 0)
704         {
705                 FreeLibrary(m_hCurrentDll);
706                 m_hCurrentDll = 0;
707                 if (m_hWnd)
708                 {
709                         std_tchar(ostringstream) stm;
710                         stm << _T("Failed to load ") << strPath.c_str();
711                         AfxMessageBox(stm.str().c_str(), MB_ICONSTOP);
712                 }
713                 return FALSE;
714         }
715         ps = 0;
716         msgid.erase();
717         lines.clear();
718         std::string format;
719         std::string msgstr;
720         std::string directive;
721         while (fgets(buf, sizeof buf, f))
722         {
723                 if (char *p = EatPrefix(buf, "#:"))
724                 {
725                         if (char *q = strchr(p, ':'))
726                         {
727                                 int line = strtol(q + 1, &q, 10);
728                                 lines.push_back(line);
729                                 --unresolved;
730                         }
731                 }
732                 else if (char *p = EatPrefix(buf, "#,"))
733                 {
734                         format = p;
735                         format.erase(0, format.find_first_not_of(" \t\r\n"));
736                         format.erase(format.find_last_not_of(" \t\r\n") + 1);
737                 }
738                 else if (char *p = EatPrefix(buf, "#."))
739                 {
740                         directive = p;
741                         directive.erase(0, directive.find_first_not_of(" \t\r\n"));
742                         directive.erase(directive.find_last_not_of(" \t\r\n") + 1);
743                 }
744                 else if (EatPrefix(buf, "msgid "))
745                 {
746                         ps = &msgid;
747                 }
748                 else if (EatPrefix(buf, "msgstr "))
749                 {
750                         ps = &msgstr;
751                 }
752                 if (ps)
753                 {
754                         char *p = strchr(buf, '"');
755                         char *q = strrchr(buf, '"');
756                         if (std::string::size_type n = q - p)
757                         {
758                                 ps->append(p + 1, n - 1);
759                         }
760                         else
761                         {
762                                 ps = 0;
763                                 if (!msgid.empty())
764                                         unslash(0, msgid);
765                                 if (msgstr.empty())
766                                         msgstr = msgid;
767                                 unslash(m_codepage, msgstr);
768                                 // avoid dereference of empty vector or last vector
769                                 if (lines.size()>0)
770                                 {
771                                         for (unsigned *pline = &*lines.begin() ; pline <= &*(lines.end() - 1) ; ++pline)
772                                         {
773                                                 unsigned line = *pline;
774                                                 if (m_strarray.size() <= line)
775                                                         m_strarray.resize(line + 1);
776                                                 if (m_strarray[line] == msgid)
777                                                         m_strarray[line] = msgstr;
778                                                 else
779                                                         ++mismatched;
780                                         }
781                                 }
782                                 lines.clear();
783                                 if (directive == "Codepage")
784                                 {
785                                         m_codepage = strtol(msgstr.c_str(), &p, 10);
786                                         directive.erase();
787                                 }
788                                 msgid.erase();
789                                 msgstr.erase();
790                         }
791                 }
792         }
793         fclose(f);
794         if (unresolved != 0 || mismatched != 0)
795         {
796                 FreeLibrary(m_hCurrentDll);
797                 m_hCurrentDll = 0;
798                 m_strarray.clear();
799                 m_map_lineno.clear();
800                 m_codepage = 0;
801                 if (m_hWnd)
802                 {
803                         std_tchar(ostringstream) stm;
804                         stm << _T("Unresolved or mismatched references detected when ")
805                                 _T("attempting to read translations from\n") << strPath.c_str();
806                         AfxMessageBox(stm.str().c_str(), MB_ICONSTOP);
807                 }
808                 return FALSE;
809         }
810         return TRUE;
811 }
812
813 /**
814  * @brief Set UI language.
815  * @param [in] wLangId 
816  * @return TRUE on success, FALSE otherwise.
817  */
818 BOOL CLanguageSelect::SetLanguage(LANGID wLangId)
819 {
820         if (wLangId == 0)
821                 return FALSE;
822         if (m_wCurLanguage == wLangId)
823                 return TRUE;
824         // reset the resource handle
825         AfxSetResourceHandle(AfxGetInstanceHandle());
826         // free the existing DLL
827         if (m_hCurrentDll)
828         {
829                 FreeLibrary(m_hCurrentDll);
830                 m_hCurrentDll = NULL;
831         }
832         m_strarray.clear();
833         m_map_lineno.clear();
834         m_codepage = 0;
835         if (wLangId != wSourceLangId)
836         {
837                 if (LoadLanguageFile(wLangId))
838                         AfxSetResourceHandle(m_hCurrentDll);
839                 else
840                         wLangId = wSourceLangId;
841         }
842         m_wCurLanguage = wLangId;
843         SetThreadLocale(MAKELCID(m_wCurLanguage, SORT_DEFAULT));
844         return TRUE;
845 }
846
847 /**
848  * @brief Get a language file for the specified language ID.
849  * This function gets a language file name for the given language ID. Language
850  * files are currently named as [languagename].po.
851  * @param [in] wLangId Language ID.
852  * @return Language filename, or empty string if no file for language found.
853  */
854 String CLanguageSelect::GetFileName(LANGID wLangId)
855 {
856         String filename;
857         String path = paths_ConcatPath(env_GetProgPath(), szRelativePath);
858         String pattern = paths_ConcatPath(path, _T("*.po"));
859         WIN32_FIND_DATA ff;
860         HANDLE h = INVALID_HANDLE_VALUE;
861         while ((h = FindFile(h, pattern.c_str(), &ff)) != INVALID_HANDLE_VALUE)
862         {
863                 filename = paths_ConcatPath(path, ff.cFileName);
864                 LangFileInfo lfi = filename.c_str();
865                 if (lfi.id == wLangId)
866                         ff.dwFileAttributes = INVALID_FILE_ATTRIBUTES; // terminate loop
867                 else
868                         filename.erase();
869         }
870         return filename;
871 }
872
873 /**
874  * @brief Check if there are language files installed.
875  *
876  * This function does as fast as possible check for installed language
877  * files. It needs to be fast since it is used in enabling/disabling
878  * GUI item(s). So the simple check we do is just find one .po file.
879  * If there is a .po file we assume we have at least one language
880  * installed.
881  * @return TRUE if at least one lang file is found. FALSE if no lang
882  * files are found.
883  */
884 BOOL CLanguageSelect::AreLangsInstalled() const
885 {
886         BOOL bFound = FALSE;
887         String path = paths_ConcatPath(env_GetProgPath(), szRelativePath);
888         String pattern = paths_ConcatPath(path, _T("*.po"));
889         WIN32_FIND_DATA ff;
890         HANDLE h = INVALID_HANDLE_VALUE;
891         while ((h = FindFile(h, pattern.c_str(), &ff)) != INVALID_HANDLE_VALUE)
892         {
893                 ff.dwFileAttributes = INVALID_FILE_ATTRIBUTES;
894                 bFound = TRUE;
895         }
896         return bFound;
897 }
898
899 /////////////////////////////////////////////////////////////////////////////
900 // CLanguageSelect commands
901
902 bool CLanguageSelect::TranslateString(size_t line, std::string &s) const
903 {
904         if (line > 0 && line < m_strarray.size())
905         {
906                 s = m_strarray[line];
907                 unsigned codepage = GetACP();
908                 if (m_codepage != codepage)
909                 {
910                         // Attempt to convert to UI codepage
911                         if (size_t len = s.length())
912                         {
913                                 std::wstring ws;
914                                 ws.resize(len);
915                                 len = MultiByteToWideChar(m_codepage, 0, s.c_str(), -1, &*ws.begin(), static_cast<int>(len) + 1);
916                                 if (len)
917                                 {
918                                         ws.resize(len - 1);
919                                         len = WideCharToMultiByte(codepage, 0, ws.c_str(), -1, 0, 0, 0, 0);
920                                         if (len)
921                                         {
922                                                 s.resize(len - 1);
923                                                 WideCharToMultiByte(codepage, 0, ws.c_str(), -1, &*s.begin(), static_cast<int>(len), 0, 0);
924                                         }
925                                 }
926                         }
927                 }
928                 return true;
929         }
930         return false;
931 }
932
933 bool CLanguageSelect::TranslateString(size_t line, std::wstring &ws) const
934 {
935         if (line > 0 && line < m_strarray.size())
936         {
937                 if (size_t len = m_strarray[line].length())
938                 {
939                         ws.resize(len);
940                         const char *msgstr = m_strarray[line].c_str();
941                         len = MultiByteToWideChar(m_codepage, 0, msgstr, -1, &*ws.begin(), static_cast<int>(len) + 1);
942                         ws.resize(len - 1);
943                         return true;
944                 }
945         }
946         return false;
947 }
948
949 bool CLanguageSelect::TranslateString(const std::string& str, String &translated_str) const
950 {
951         EngLinenoMap::const_iterator it = m_map_lineno.find(str);
952         if (it != m_map_lineno.end())
953         {
954                 return TranslateString(it->second, translated_str);
955         }
956         translated_str = ucr::toTString(str);
957         return false;
958 }
959
960 void CLanguageSelect::SetIndicators(CStatusBar &sb, const UINT *rgid, int n) const
961 {
962         HGDIOBJ hf = (HGDIOBJ)sb.SendMessage(WM_GETFONT);
963         CClientDC dc(0);
964         if (hf)
965                 hf = dc.SelectObject(hf);
966         if (n)
967                 sb.SetIndicators(0, n);
968         else
969                 n = sb.m_nCount;
970         int cx = ::GetSystemMetrics(SM_CXSCREEN) / 4;   // default to 1/4 the screen width
971         UINT style = SBPS_STRETCH | SBPS_NOBORDERS;             // first pane is stretchy
972         for (int i = 0 ; i < n ; ++i)
973         {
974                 UINT id = rgid ? rgid[i] : sb.GetItemID(i);
975                 if (id >= ID_INDICATOR_EXT)
976                 {
977                         String text = LoadString(id);
978                         size_t cx = dc.GetTextExtent(text.c_str(), static_cast<int>(text.length())).cx;
979                         sb.SetPaneInfo(i, id, style | SBPS_DISABLED, static_cast<int>(cx));
980                         sb.SetPaneText(i, text.c_str(), FALSE);
981                 }
982                 else if (rgid)
983                 {
984                         sb.SetPaneInfo(i, 0, style, cx);
985                 }
986                 style = 0;
987         }
988         if (hf)
989                 hf = dc.SelectObject(hf);
990         // Send WM_SIZE to get pane rectangles right
991         RECT rect;
992         sb.GetClientRect(&rect);
993         sb.SendMessage(WM_SIZE, 0, MAKELPARAM(rect.right, rect.bottom));
994 }
995
996 void CLanguageSelect::TranslateMenu(HMENU h) const
997 {
998         int i = ::GetMenuItemCount(h);
999         while (i > 0)
1000         {
1001                 --i;
1002                 MENUITEMINFO mii = {0};
1003 #if(WINVER >= 0x0500)
1004                 mii.cbSize = sizeof mii - sizeof HBITMAP;
1005 #else
1006                 mii.cbSize = sizeof mii;
1007 #endif
1008                 mii.fMask = MIIM_STATE|MIIM_ID|MIIM_SUBMENU|MIIM_DATA;
1009                 ::GetMenuItemInfo(h, i, TRUE, &mii);
1010                 if (mii.hSubMenu)
1011                 {
1012                         TranslateMenu(mii.hSubMenu);
1013                         mii.wID = reinterpret_cast<UINT>(mii.hSubMenu);
1014                 }
1015                 if (BCMenuData *pItemData = reinterpret_cast<BCMenuData *>(mii.dwItemData))
1016                 {
1017                         if (LPCWSTR text = pItemData->GetWideString())
1018                         {
1019                                 unsigned line = 0;
1020                                 swscanf(text, L"Merge.rc:%u", &line);
1021                                 std::wstring s;
1022                                 if (TranslateString(line, s))
1023                                         pItemData->SetWideString(s.c_str());
1024                         }
1025                 }
1026                 TCHAR text[80];
1027                 if (::GetMenuString(h, i, text, countof(text), MF_BYPOSITION))
1028                 {
1029                         unsigned line = 0;
1030                         _stscanf(text, _T("Merge.rc:%u"), &line);
1031                         String s;
1032                         if (TranslateString(line, s))
1033                                 ::ModifyMenu(h, i, mii.fState | MF_BYPOSITION, mii.wID, s.c_str());
1034                 }
1035         }
1036 }
1037
1038 void CLanguageSelect::TranslateDialog(HWND h) const
1039 {
1040         UINT gw = GW_CHILD;
1041         do
1042         {
1043                 TCHAR text[80];
1044                 ::GetWindowText(h, text, countof(text));
1045                 unsigned line = 0;
1046                 _stscanf(text, _T("Merge.rc:%u"), &line);
1047                 String s;
1048                 if (TranslateString(line, s))
1049                         ::SetWindowText(h, s.c_str());
1050                 h = ::GetWindow(h, gw);
1051                 gw = GW_HWNDNEXT;
1052         } while (h);
1053 }
1054
1055 String CLanguageSelect::LoadString(UINT id) const
1056 {
1057         String s;
1058         if (id)
1059         {
1060                 TCHAR text[1024];
1061                 AfxLoadString(id, text, countof(text));
1062                 unsigned line = 0;
1063                 _stscanf(text, _T("Merge.rc:%u"), &line);
1064                 if (!TranslateString(line, s))
1065                         s = text;
1066         }
1067         return s;
1068 }
1069
1070 std::wstring CLanguageSelect::LoadDialogCaption(LPCTSTR lpDialogTemplateID) const
1071 {
1072         std::wstring s;
1073         if (HINSTANCE hInst = AfxFindResourceHandle(lpDialogTemplateID, RT_DIALOG))
1074         {
1075                 if (HRSRC hRsrc = FindResource(hInst, lpDialogTemplateID, RT_DIALOG))
1076                 {
1077                         if (LPCWSTR text = (LPCWSTR)LoadResource(hInst, hRsrc))
1078                         {
1079                                 // Skip DLGTEMPLATE or DLGTEMPLATEEX
1080                                 text += text[1] == 0xFFFF ? 13 : 9;
1081                                 // Skip menu name string or ordinal
1082                                 if (*text == (const WCHAR)-1)
1083                                         text += 2; // WCHARs
1084                                 else
1085                                         while (*text++);
1086                                 // Skip class name string or ordinal
1087                                 if (*text == (const WCHAR)-1)
1088                                         text += 2; // WCHARs
1089                                 else
1090                                         while (*text++);
1091                                 // Caption string is ahead
1092                                 unsigned line = 0;
1093                                 swscanf(text, L"Merge.rc:%u", &line);
1094                                 if (!TranslateString(line, s))
1095                                         s = text;
1096                         }
1097                 }
1098         }
1099         return s;
1100 }
1101
1102 void CLanguageSelect::ReloadMenu() 
1103 {
1104         if (m_idDocMenu)
1105         {
1106                 // set the menu of the main frame window
1107                 UINT idMenu = GetDocResId();
1108                 CMergeApp *pApp = dynamic_cast<CMergeApp *> (AfxGetApp());
1109                 CMainFrame * pMainFrame = dynamic_cast<CMainFrame *> ((CFrameWnd*)pApp->m_pMainWnd);
1110                 HMENU hNewDefaultMenu = pMainFrame->NewDefaultMenu(idMenu);
1111                 HMENU hNewMergeMenu = pMainFrame->NewMergeViewMenu();
1112                 HMENU hNewDirMenu = pMainFrame->NewDirViewMenu();
1113                 if (hNewDefaultMenu && hNewMergeMenu && hNewDirMenu)
1114                 {
1115                         // Note : for Windows98 compatibility, use FromHandle and not Attach/Detach
1116                         CMenu * pNewDefaultMenu = CMenu::FromHandle(hNewDefaultMenu);
1117                         CMenu * pNewMergeMenu = CMenu::FromHandle(hNewMergeMenu);
1118                         CMenu * pNewDirMenu = CMenu::FromHandle(hNewDirMenu);
1119                         
1120                         CWnd *pFrame = CWnd::FromHandle(::GetWindow(pMainFrame->m_hWndMDIClient, GW_CHILD));
1121                         while (pFrame)
1122                         {
1123                                 if (pFrame->IsKindOf(RUNTIME_CLASS(CChildFrame)))
1124                                         ((CChildFrame *)pFrame)->SetSharedMenu(hNewMergeMenu);
1125                                 else if (pFrame->IsKindOf(RUNTIME_CLASS(COpenFrame)))
1126                                         ((COpenFrame *)pFrame)->SetSharedMenu(hNewDefaultMenu);
1127                                 else if (pFrame->IsKindOf(RUNTIME_CLASS(CDirFrame)))
1128                                         ((CDirFrame *)pFrame)->SetSharedMenu(hNewDirMenu);
1129                                 pFrame = pFrame->GetNextWindow();
1130                         }
1131
1132                         CFrameWnd *pActiveFrame = pMainFrame->GetActiveFrame();
1133                         if (pActiveFrame)
1134                         {
1135                                 if (pActiveFrame->IsKindOf(RUNTIME_CLASS(CChildFrame)))
1136                                         pMainFrame->MDISetMenu(pNewMergeMenu, NULL);
1137                                 else if (pActiveFrame->IsKindOf(RUNTIME_CLASS(CDirFrame)))
1138                                         pMainFrame->MDISetMenu(pNewDirMenu, NULL);
1139                                 else
1140                                         pMainFrame->MDISetMenu(pNewDefaultMenu, NULL);
1141                         }
1142                         else
1143                                 pMainFrame->MDISetMenu(pNewDefaultMenu, NULL);
1144
1145                         // Don't delete the old menu
1146                         // There is a bug in BCMenu or in Windows98 : the new menu does not
1147                         // appear correctly if we destroy the old one
1148 //                      if (pOldDefaultMenu)
1149 //                              pOldDefaultMenu->DestroyMenu();
1150 //                      if (pOldMergeMenu)
1151 //                              pOldMergeMenu->DestroyMenu();
1152 //                      if (pOldDirMenu)
1153 //                              pOldDirMenu->DestroyMenu();
1154
1155                         // m_hMenuDefault is used to redraw the main menu when we close a child frame
1156                         // if this child frame had a different menu
1157                         pMainFrame->m_hMenuDefault = hNewDefaultMenu;
1158                         pApp->m_pOpenTemplate->m_hMenuShared = hNewDefaultMenu;
1159                         pApp->m_pDiffTemplate->m_hMenuShared = hNewMergeMenu;
1160                         pApp->m_pDirTemplate->m_hMenuShared = hNewDirMenu;
1161
1162                         // force redrawing the menu bar
1163                         pMainFrame->DrawMenuBar();  
1164
1165                 }
1166         }
1167 }
1168
1169
1170 UINT CLanguageSelect::GetDocResId()
1171 {
1172         if (((CMDIFrameWnd*)AfxGetApp()->m_pMainWnd)->MDIGetActive())
1173                 return m_idDocMenu;
1174         
1175         return m_idMainMenu;
1176 }
1177
1178
1179 void CLanguageSelect::UpdateDocTitle()
1180 {
1181         CDocManager* pDocManager = AfxGetApp()->m_pDocManager;
1182         POSITION posTemplate = pDocManager->GetFirstDocTemplatePosition();
1183         ASSERT(posTemplate != NULL);
1184
1185         while (posTemplate != NULL)
1186         {
1187                 CDocTemplate* pTemplate = pDocManager->GetNextDocTemplate(posTemplate);
1188                 
1189                 ASSERT(pTemplate != NULL);
1190                 
1191                 POSITION pos = pTemplate->GetFirstDocPosition();
1192                 CDocument* pDoc;
1193                 
1194                 while ( pos != NULL  )
1195                 {
1196                         pDoc = pTemplate->GetNextDoc(pos);
1197                         pDoc->SetTitle(NULL);
1198                         ((CFrameWnd*)AfxGetApp()->m_pMainWnd)->OnUpdateFrameTitle(TRUE);
1199                 }
1200         }
1201
1202
1203
1204
1205 void CLanguageSelect::OnOK() 
1206 {
1207         UpdateData();
1208         int index = m_ctlLangList.GetCurSel();
1209         if (index<0) return;
1210         //int i = m_ctlLangList.GetItemData(index);
1211         WORD lang = (WORD)m_ctlLangList.GetItemData(index); //m_wLangIds[i];
1212         if (lang != m_wCurLanguage)
1213         {
1214                 if (SetLanguage(lang))
1215                         GetOptionsMgr()->SaveOption(OPT_SELECTED_LANGUAGE, (int)lang);
1216
1217                 theApp.UpdateCodepageModule();
1218
1219                 // Update status bar inicator texts
1220                 SetIndicators(GetMainFrame()->m_wndStatusBar, 0, 0);
1221
1222                 // Update the current menu
1223                 if (m_bReloadMenu)
1224                         ReloadMenu();
1225                 
1226                 // update the title text of the document
1227                 if (m_bUpdateTitle)
1228                         UpdateDocTitle();
1229         }
1230         
1231         EndDialog(IDOK);
1232 }
1233
1234 BOOL CLanguageSelect::OnInitDialog()
1235 {
1236         TranslateDialog(m_hWnd);
1237         CDialog::OnInitDialog();
1238
1239         // setup handler for resizing this dialog       
1240         m_constraint.InitializeCurrentSize(this);
1241         // configure how individual controls adjust when dialog resizes
1242         m_constraint.ConstrainItem(IDC_LANGUAGE_LIST, 0, 1, 0, 1); // grows right & down
1243         m_constraint.ConstrainItem(IDCANCEL, .6, 0, 1, 0); // slides down, floats right
1244         m_constraint.ConstrainItem(IDOK, .3, 0, 1, 0); // slides down, floats right
1245         m_constraint.SubclassWnd(); // install subclassing
1246         m_constraint.LoadPosition(_T("ResizeableDialogs"), _T("LanguageSelectDlg"), false); // persist size via registry
1247
1248         AfxGetMainWnd()->CenterWindow(this);
1249
1250         LoadAndDisplayLanguages();
1251
1252         return TRUE;
1253 }
1254
1255 /**
1256  * @brief Load languages available on disk, and display in list, and select current
1257  */
1258 void CLanguageSelect::LoadAndDisplayLanguages()
1259 {
1260         String path = paths_ConcatPath(env_GetProgPath(), szRelativePath);
1261         String pattern = paths_ConcatPath(path, _T("*.po"));
1262         WIN32_FIND_DATA ff;
1263         HANDLE h = INVALID_HANDLE_VALUE;
1264         do
1265         {
1266                 LangFileInfo &lfi =
1267                         h == INVALID_HANDLE_VALUE
1268                 ?       LangFileInfo(wSourceLangId)
1269                 :       LangFileInfo(paths_ConcatPath(path, ff.cFileName).c_str());
1270                 std_tchar(ostringstream) stm;
1271                 stm << lfi.GetString(LOCALE_SLANGUAGE).c_str();
1272                 stm << _T(" - ");
1273                 stm << lfi.GetString(LOCALE_SNATIVELANGNAME|LOCALE_USE_CP_ACP).c_str();
1274                 stm << _T(" (");
1275                 stm << lfi.GetString(LOCALE_SNATIVECTRYNAME|LOCALE_USE_CP_ACP).c_str();
1276                 stm << _T(")");
1277                 /*stm << _T(" - ");
1278                 stm << lfi.GetString(LOCALE_SABBREVLANGNAME|LOCALE_USE_CP_ACP).c_str();
1279                 stm << _T(" (");
1280                 stm << lfi.GetString(LOCALE_SABBREVCTRYNAME|LOCALE_USE_CP_ACP).c_str();
1281                 stm << _T(") ");*/
1282                 stm << _T(" - ");
1283                 stm << lfi.GetString(LOCALE_SENGLANGUAGE).c_str();
1284                 stm << _T(" (");
1285                 stm << lfi.GetString(LOCALE_SENGCOUNTRY).c_str();
1286                 stm << _T(")");
1287                 int i = m_ctlLangList.AddString(stm.str().c_str());
1288                 m_ctlLangList.SetItemData(i, lfi.id);
1289                 if (lfi.id == m_wCurLanguage)
1290                         m_ctlLangList.SetCurSel(i);
1291         } while ((h = FindFile(h, pattern.c_str(), &ff)) != INVALID_HANDLE_VALUE);
1292 }
1293
1294 /**
1295  * @brief Find DLL entry in lang_map for language for specified locale
1296  */
1297 static WORD GetLangFromLocale(LCID lcid)
1298 {
1299         TCHAR buff[8] = {0};
1300         WORD langID = 0;
1301         if (GetLocaleInfo(lcid, LOCALE_IDEFAULTLANGUAGE, buff, countof(buff)))
1302                 _stscanf(buff, _T("%4hx"), &langID);
1303         return langID;
1304 }
1305
1306 void CLanguageSelect::InitializeLanguage()
1307 {
1308         ASSERT(LangFileInfo::LangId("GERMAN", "") == MAKELANGID(LANG_GERMAN, SUBLANG_GERMAN));
1309         ASSERT(LangFileInfo::LangId("GERMAN", "DEFAULT") == MAKELANGID(LANG_GERMAN, SUBLANG_DEFAULT));
1310         ASSERT(LangFileInfo::LangId("GERMAN", "SWISS") == MAKELANGID(LANG_GERMAN, SUBLANG_GERMAN_SWISS));
1311         ASSERT(LangFileInfo::LangId("PORTUGUESE", "") == MAKELANGID(LANG_PORTUGUESE, SUBLANG_PORTUGUESE));
1312         ASSERT(LangFileInfo::LangId("NORWEGIAN", "BOKMAL") == MAKELANGID(LANG_NORWEGIAN, SUBLANG_NORWEGIAN_BOKMAL));
1313         ASSERT(LangFileInfo::LangId("NORWEGIAN", "NYNORSK") == MAKELANGID(LANG_NORWEGIAN, SUBLANG_NORWEGIAN_NYNORSK));
1314
1315         //TRACE(_T("%hs\n"), LangFileInfo::FileName(MAKELANGID(LANG_NORWEGIAN, SUBLANG_NORWEGIAN_BOKMAL)).c_str());
1316         //TRACE(_T("%hs\n"), LangFileInfo::FileName(MAKELANGID(LANG_PORTUGUESE, SUBLANG_PORTUGUESE)).c_str());
1317         //TRACE(_T("%hs\n"), LangFileInfo::FileName(MAKELANGID(LANG_GERMAN, SUBLANG_DEFAULT)).c_str());
1318
1319         WORD langID = (WORD)GetOptionsMgr()->GetInt(OPT_SELECTED_LANGUAGE);
1320         if (langID)
1321         {
1322                 // User has set a language override
1323                 SetLanguage(langID);
1324                 return;
1325         }
1326         // User has not specified a language
1327         // so look in thread locale, user locale, and then system locale for
1328         // a language that WinMerge supports
1329         WORD Lang1 = GetLangFromLocale(GetThreadLocale());
1330         if (SetLanguage(Lang1))
1331                 return;
1332         WORD Lang2 = GetLangFromLocale(LOCALE_USER_DEFAULT);
1333         if (Lang2 != Lang1 && SetLanguage(Lang2))
1334                 return;
1335         WORD Lang3 = GetLangFromLocale(LOCALE_SYSTEM_DEFAULT);
1336         if (Lang3 != Lang2 && Lang3 != Lang1 && SetLanguage(Lang3))
1337                 return;
1338 }