2 * @file LanguageSelect.cpp
4 * @brief Implements the Language Selection dialog class (which contains the language data)
8 #include "LanguageSelect.h"
12 #include "Environment.h"
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")
21 static char THIS_FILE[] = __FILE__;
24 /** @brief Relative path to WinMerge executable for lang files. */
25 static const TCHAR szRelativePath[] = _T("\\Languages\\");
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);
32 * @brief A class holding information about language file.
37 LANGID id; /**< Language ID. */
39 static LANGID LangId(const char *lang, const char *sublang);
42 * A constructor taking a language id as parameter.
43 * @param [in] id Language ID to use.
45 LangFileInfo(LANGID id): id(id) { };
47 LangFileInfo(LPCTSTR path);
48 String GetString(LCTYPE type) const;
56 static const struct rg rg[];
60 * @brief An array holding language IDs and names.
62 const struct LangFileInfo::rg LangFileInfo::rg[] =
65 LANG_AFRIKAANS, "AFRIKAANS\0"
68 LANG_ALBANIAN, "ALBANIAN\0"
71 LANG_ARABIC, "ARABIC\0" "SAUDI_ARABIA\0"
89 LANG_ARMENIAN, "ARMENIAN\0"
92 LANG_ASSAMESE, "ASSAMESE\0"
95 LANG_AZERI, "AZERI\0" "LATIN\0"
99 LANG_BASQUE, "BASQUE\0"
102 LANG_BELARUSIAN, "BELARUSIAN\0"
105 LANG_BENGALI, "BENGALI\0"
108 LANG_BULGARIAN, "BULGARIAN\0"
111 LANG_CATALAN, "CATALAN\0"
114 LANG_CHINESE, "CHINESE\0" "TRADITIONAL\0"
121 LANG_CROATIAN, "CROATIAN\0"
124 LANG_CZECH, "CZECH\0"
127 LANG_DANISH, "DANISH\0"
130 LANG_DIVEHI, "DIVEHI\0"
133 MAKELANGID(LANG_DUTCH, SUBLANG_DUTCH), "DUTCH\0"
137 LANG_ENGLISH, "ENGLISH\0" "US\0"
152 LANG_ESTONIAN, "ESTONIAN\0"
155 LANG_FAEROESE, "FAEROESE\0"
158 LANG_FARSI, "FARSI\0"
161 LANG_FINNISH, "FINNISH\0"
164 MAKELANGID(LANG_FRENCH, SUBLANG_FRENCH), "FRENCH\0"
172 LANG_GALICIAN, "GALICIAN\0"
175 LANG_GEORGIAN, "GEORGIAN\0"
178 MAKELANGID(LANG_GERMAN, SUBLANG_GERMAN), "GERMAN\0"
185 LANG_GREEK, "GREEK\0"
188 LANG_GUJARATI, "GUJARATI\0"
191 LANG_HEBREW, "HEBREW\0"
194 LANG_HINDI, "HINDI\0"
197 LANG_HUNGARIAN, "HUNGARIAN\0"
200 LANG_ICELANDIC, "ICELANDIC\0"
203 LANG_INDONESIAN, "INDONESIAN\0"
206 MAKELANGID(LANG_ITALIAN, SUBLANG_ITALIAN), "ITALIAN\0"
210 LANG_JAPANESE, "JAPANESE\0"
213 LANG_KANNADA, "KANNADA\0"
216 MAKELANGID(LANG_KASHMIRI, SUBLANG_DEFAULT), "KASHMIRI\0"
220 LANG_KAZAK, "KAZAK\0"
223 LANG_KONKANI, "KONKANI\0"
226 MAKELANGID(LANG_KOREAN, SUBLANG_KOREAN), "KOREAN\0"
229 LANG_KYRGYZ, "KYRGYZ\0"
232 LANG_LATVIAN, "LATVIAN\0"
235 MAKELANGID(LANG_LITHUANIAN, SUBLANG_LITHUANIAN), "LITHUANIAN\0"
238 LANG_MACEDONIAN, "MACEDONIAN\0"
241 LANG_MALAY, "MALAY\0" "MALAYSIA\0"
242 "BRUNEI_DARUSSALAM\0"
245 LANG_MALAYALAM, "MALAYALAM\0"
248 LANG_MANIPURI, "MANIPURI\0"
251 LANG_MARATHI, "MARATHI\0"
254 LANG_MONGOLIAN, "MONGOLIAN\0"
257 MAKELANGID(LANG_NEPALI, SUBLANG_DEFAULT), "NEPALI\0"
261 LANG_NORWEGIAN, "NORWEGIAN\0" "BOKMAL\0"
265 LANG_ORIYA, "ORIYA\0"
268 LANG_POLISH, "POLISH\0"
271 MAKELANGID(LANG_PORTUGUESE, SUBLANG_PORTUGUESE), "PORTUGUESE\0"
275 LANG_PUNJABI, "PUNJABI\0"
278 LANG_ROMANIAN, "ROMANIAN\0"
281 LANG_RUSSIAN, "RUSSIAN\0"
284 LANG_SANSKRIT, "SANSKRIT\0"
287 MAKELANGID(LANG_SERBIAN, SUBLANG_DEFAULT), "SERBIAN\0"
292 LANG_SINDHI, "SINDHI\0"
295 LANG_SLOVAK, "SLOVAK\0"
298 LANG_SLOVENIAN, "SLOVENIAN\0"
301 MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH), "SPANISH\0"
307 "DOMINICAN_REPUBLIC\0"
323 LANG_SWAHILI, "SWAHILI\0"
326 MAKELANGID(LANG_SWEDISH, SUBLANG_SWEDISH), "SWEDISH\0"
330 LANG_SYRIAC, "SYRIAC\0"
333 LANG_TAMIL, "TAMIL\0"
336 LANG_TATAR, "TATAR\0"
339 LANG_TELUGU, "TELUGU\0"
345 LANG_TURKISH, "TURKISH\0"
348 LANG_UKRAINIAN, "UKRAINIAN\0"
351 LANG_URDU, "URDU\0" "PAKISTAN\0"
355 LANG_UZBEK, "UZBEK\0" "LATIN\0"
359 LANG_VIETNAMESE, "VIETNAMESE\0"
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.
369 LANGID LangFileInfo::LangId(const char *lang, const char *sublang)
371 // binary search the array for passed in lang
373 size_t upper = countof(rg);
374 while (lower < upper)
376 size_t match = (upper + lower) >> 1;
377 int cmp = strcmp(rg[match].lang, lang);
385 LANGID baseid = rg[upper].id;
386 // figure out sublang
387 if ((baseid & ~0x3ff) && *sublang == '\0')
389 LANGID id = PRIMARYLANGID(baseid);
390 if (0 == strcmp(sublang, "DEFAULT"))
391 return MAKELANGID(id, SUBLANG_DEFAULT);
392 const char *sub = rg[upper].lang;
397 id += MAKELANGID(0, 1);
398 } while (id == baseid);
399 sub += strlen(sub) + 1;
400 if (0 == strcmp(sublang, sub))
407 * @brief A constructor taking a path to language file as parameter.
408 * @param [in] path Full path to the language file.
410 LangFileInfo::LangFileInfo(LPCTSTR path)
413 if (FILE *f = _tfopen(path, _T("r")))
416 while (fgets(buf, sizeof buf - 1, f))
420 sscanf(buf, "msgid \" LANG_ENGLISH , SUBLANG_ENGLISH_US \" %d", &i);
423 if (fgets(buf, sizeof buf, f))
425 char *lang = strstr(buf, "LANG_");
426 char *sublang = strstr(buf, "SUBLANG_");
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"))
435 sublang = EatPrefix(sublang, lang);
436 if (sublang && *sublang)
437 sublang = EatPrefix(sublang, "_");
440 id = LangId(lang, sublang);
450 String LangFileInfo::GetString(LCTYPE type) const
453 if (int cch = GetLocaleInfo(id, type, 0, 0))
456 GetLocaleInfo(id, type, &*s.begin(), cch);
461 static HANDLE NTAPI FindFile(HANDLE h, LPCTSTR path, WIN32_FIND_DATA *fd)
463 if (h == INVALID_HANDLE_VALUE)
465 h = FindFirstFile(path, fd);
467 else if (fd->dwFileAttributes == INVALID_FILE_ATTRIBUTES || !FindNextFile(h, fd))
470 h = INVALID_HANDLE_VALUE;
475 /////////////////////////////////////////////////////////////////////////////
476 // CLanguageSelect dialog
478 /** @brief Default English language. */
479 const WORD wSourceLangId = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);
481 CLanguageSelect::CLanguageSelect()
483 , m_wCurLanguage(wSourceLangId)
485 SetThreadLocale(MAKELCID(m_wCurLanguage, SORT_DEFAULT));
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.
496 static char *EatPrefix(char *text, const char *prefix)
498 if (size_t len = strlen(prefix))
499 if (_memicmp(text, prefix, len) == 0)
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.
509 static void unslash(unsigned codepage, std::string &s)
511 char *p = &*s.begin();
544 *p = (char)strtol(r, &q, 16);
547 *p = (char)strtol(r - 1, &q, 8);
555 if ((*p & 0x80) && IsDBCSLeadByteEx(codepage, *p))
561 s.resize(p - 1 - &*s.begin());
565 * @brief Load language.file
566 * @param [in] wLangId
567 * @return TRUE on success, FALSE otherwise.
569 BOOL CLanguageSelect::LoadLanguageFile(LANGID wLangId, BOOL bShowError)
571 String strPath = GetFileName(wLangId);
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)
581 AfxMessageBox(_T("Failed to load MergeLang.dll"), MB_ICONSTOP);
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)
594 FreeLibrary(m_hCurrentDll);
597 AfxMessageBox(_T("MergeLang.dll version mismatch"), MB_ICONSTOP);
600 HRSRC mergepot = FindResource(m_hCurrentDll, _T("MERGEPOT"), RT_RCDATA);
604 AfxMessageBox(_T("MergeLang.dll is invalid"), MB_ICONSTOP);
607 size_t size = SizeofResource(m_hCurrentDll, mergepot);
608 const char *data = (const char *)LoadResource(m_hCurrentDll, mergepot);
612 std::vector<unsigned> lines;
615 while (const char *eol = (const char *)memchr(data, '\n', size))
617 size_t len = eol - data;
618 if (len >= sizeof buf)
623 memcpy(buf, data, len);
627 if (char *p = EatPrefix(buf, "#:"))
629 if (char *q = strchr(p, ':'))
631 int line = strtol(q + 1, &q, 10);
632 lines.push_back(line);
636 else if (EatPrefix(buf, "msgid "))
642 char *p = strchr(buf, '"');
643 char *q = strrchr(buf, '"');
644 if (std::string::size_type n = q - p)
646 ps->append(p + 1, n - 1);
651 // avoid dereference of empty vector or last vector
652 if (lines.size() > 0)
654 for (unsigned *pline = &*lines.begin() ; pline <= &*(lines.end() - 1) ; ++pline)
656 unsigned line = *pline;
657 if (m_strarray.size() <= line)
658 m_strarray.resize(line + 1);
659 m_strarray[line] = msgid;
667 FILE *f = _tfopen(strPath.c_str(), _T("r"));
670 FreeLibrary(m_hCurrentDll);
674 String str = _T("Failed to load ") + strPath;
675 AfxMessageBox(str.c_str(), MB_ICONSTOP);
684 std::string directive;
685 while (fgets(buf, sizeof buf, f))
687 if (char *p = EatPrefix(buf, "#:"))
689 if (char *q = strchr(p, ':'))
691 int line = strtol(q + 1, &q, 10);
692 lines.push_back(line);
696 else if (char *p = EatPrefix(buf, "#,"))
699 format.erase(0, format.find_first_not_of(" \t\r\n"));
700 format.erase(format.find_last_not_of(" \t\r\n") + 1);
702 else if (char *p = EatPrefix(buf, "#."))
705 directive.erase(0, directive.find_first_not_of(" \t\r\n"));
706 directive.erase(directive.find_last_not_of(" \t\r\n") + 1);
708 else if (EatPrefix(buf, "msgid "))
712 else if (EatPrefix(buf, "msgstr "))
718 char *p = strchr(buf, '"');
719 char *q = strrchr(buf, '"');
720 if (std::string::size_type n = q - p)
722 ps->append(p + 1, n - 1);
729 unslash(m_codepage, msgstr);
730 // avoid dereference of empty vector or last vector
733 for (unsigned *pline = &*lines.begin() ; pline <= &*(lines.end() - 1) ; ++pline)
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;
745 if (directive == "Codepage")
747 m_codepage = strtol(msgstr.c_str(), &p, 10);
756 if (unresolved != 0 || mismatched != 0)
758 FreeLibrary(m_hCurrentDll);
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);
774 * @brief Set UI language.
775 * @param [in] wLangId
776 * @return TRUE on success, FALSE otherwise.
778 BOOL CLanguageSelect::SetLanguage(LANGID wLangId, BOOL bShowError)
782 if (m_wCurLanguage == wLangId)
784 // reset the resource handle
785 AfxSetResourceHandle(AfxGetInstanceHandle());
786 // free the existing DLL
789 FreeLibrary(m_hCurrentDll);
790 m_hCurrentDll = NULL;
794 if (wLangId != wSourceLangId)
796 if (LoadLanguageFile(wLangId, bShowError))
797 AfxSetResourceHandle(m_hCurrentDll);
799 wLangId = wSourceLangId;
801 m_wCurLanguage = wLangId;
802 SetThreadLocale(MAKELCID(m_wCurLanguage, SORT_DEFAULT));
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.
813 String CLanguageSelect::GetFileName(LANGID wLangId) const
816 String path = env_GetProgPath().append(szRelativePath);
817 String pattern = path + _T("*.po");
819 HANDLE h = INVALID_HANDLE_VALUE;
820 while ((h = FindFile(h, pattern.c_str(), &ff)) != INVALID_HANDLE_VALUE)
822 filename = path + ff.cFileName;
823 LangFileInfo lfi = filename.c_str();
824 if (lfi.id == wLangId)
825 ff.dwFileAttributes = INVALID_FILE_ATTRIBUTES; // terminate loop
832 /////////////////////////////////////////////////////////////////////////////
833 // CLanguageSelect commands
835 bool CLanguageSelect::TranslateString(size_t line, std::string &s) const
837 if (line > 0 && line < m_strarray.size())
839 s = m_strarray[line];
840 unsigned codepage = GetACP();
841 if (m_codepage != codepage)
843 // Attempt to convert to UI codepage
844 if (int len = static_cast<int>(s.length()))
848 len = MultiByteToWideChar(m_codepage, 0, s.c_str(), -1, &*ws.begin(), len + 1);
852 len = WideCharToMultiByte(codepage, 0, ws.c_str(), -1, 0, 0, 0, 0);
856 WideCharToMultiByte(codepage, 0, ws.c_str(), -1, &*s.begin(), len, 0, 0);
866 bool CLanguageSelect::TranslateString(size_t line, std::wstring &ws) const
868 if (line > 0 && line < m_strarray.size())
870 if (int len = static_cast<int>(m_strarray[line].length()))
873 const char *msgstr = m_strarray[line].c_str();
874 len = MultiByteToWideChar(m_codepage, 0, msgstr, -1, &*ws.begin(), len + 1);
882 void CLanguageSelect::SetIndicators(CStatusBar &sb, const UINT *rgid, int n) const
884 HGDIOBJ hf = (HGDIOBJ)sb.SendMessage(WM_GETFONT);
887 hf = dc.SelectObject(hf);
889 sb.SetIndicators(0, n);
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)
896 UINT id = rgid ? rgid[i] : sb.GetItemID(i);
897 if (id >= ID_INDICATOR_EXT)
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);
906 sb.SetPaneInfo(i, 0, style, cx);
911 hf = dc.SelectObject(hf);
912 // Send WM_SIZE to get pane rectangles right
914 sb.GetClientRect(&rect);
915 sb.SendMessage(WM_SIZE, 0, MAKELPARAM(rect.right, rect.bottom));
918 void CLanguageSelect::TranslateMenu(HMENU h) const
920 int i = ::GetMenuItemCount(h);
924 MENUITEMINFO mii = {0};
925 #if(WINVER >= 0x0500)
926 mii.cbSize = sizeof mii - sizeof HBITMAP;
928 mii.cbSize = sizeof mii;
930 mii.fMask = MIIM_STATE|MIIM_ID|MIIM_SUBMENU|MIIM_DATA;
931 ::GetMenuItemInfo(h, i, TRUE, &mii);
934 TranslateMenu(mii.hSubMenu);
935 mii.wID = reinterpret_cast<UINT>(mii.hSubMenu);
937 if (BCMenuData *pItemData = reinterpret_cast<BCMenuData *>(mii.dwItemData))
939 if (LPCWSTR text = pItemData->GetWideString())
942 swscanf(text, L"Merge.rc:%u", &line);
944 if (TranslateString(line, s))
945 pItemData->SetWideString(s.c_str());
949 if (::GetMenuString(h, i, text, countof(text), MF_BYPOSITION))
952 _stscanf(text, _T("Merge.rc:%u"), &line);
954 if (TranslateString(line, s))
955 ::ModifyMenu(h, i, mii.fState | MF_BYPOSITION, mii.wID, s.c_str());
960 void CLanguageSelect::TranslateDialog(HWND h) const
966 ::GetWindowText(h, text, countof(text));
968 _stscanf(text, _T("Merge.rc:%u"), &line);
970 if (TranslateString(line, s))
971 ::SetWindowText(h, s.c_str());
972 h = ::GetWindow(h, gw);
977 String CLanguageSelect::LoadString(UINT id) const
983 AfxLoadString(id, text, countof(text));
985 _stscanf(text, _T("Merge.rc:%u"), &line);
986 if (!TranslateString(line, s))
992 std::wstring CLanguageSelect::LoadDialogCaption(LPCTSTR lpDialogTemplateID) const
995 if (HINSTANCE hInst = AfxFindResourceHandle(lpDialogTemplateID, RT_DIALOG))
997 if (HRSRC hRsrc = FindResource(hInst, lpDialogTemplateID, RT_DIALOG))
999 if (LPCWSTR text = (LPCWSTR)LoadResource(hInst, hRsrc))
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
1008 // Skip class name string or ordinal
1009 if (*text == (const WCHAR)-1)
1010 text += 2; // WCHARs
1013 // Caption string is ahead
1015 swscanf(text, L"Merge.rc:%u", &line);
1016 if (!TranslateString(line, s))
1025 * @brief Load languages available on disk, and display in list, and select current
1027 std::vector<std::pair<LANGID, String> > CLanguageSelect::GetAvailableLanguages() const
1029 std::vector<std::pair<LANGID, String> > list;
1030 String path = env_GetProgPath().append(szRelativePath);
1031 String pattern = path + _T("*.po");
1033 HANDLE h = INVALID_HANDLE_VALUE;
1037 h == INVALID_HANDLE_VALUE
1038 ? LangFileInfo(wSourceLangId)
1039 : LangFileInfo((path + ff.cFileName).c_str());
1041 str += lfi.GetString(LOCALE_SLANGUAGE);
1043 str += lfi.GetString(LOCALE_SNATIVELANGNAME | LOCALE_USE_CP_ACP);
1045 str += lfi.GetString(LOCALE_SNATIVECTRYNAME | LOCALE_USE_CP_ACP);
1048 str += lfi.GetString(LOCALE_SENGLANGUAGE);
1050 str += lfi.GetString(LOCALE_SENGCOUNTRY);
1052 list.emplace_back(lfi.id, str);
1053 } while ((h = FindFile(h, pattern.c_str(), &ff)) != INVALID_HANDLE_VALUE);
1058 * @brief Find DLL entry in lang_map for language for specified locale
1060 static WORD GetLangFromLocale(LCID lcid)
1062 TCHAR buff[8] = {0};
1064 if (GetLocaleInfo(lcid, LOCALE_IDEFAULTLANGUAGE, buff, countof(buff)))
1065 _stscanf(buff, _T("%4hx"), &langID);
1069 void CLanguageSelect::InitializeLanguage(WORD langID)
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));
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());
1084 // User has set a language override
1085 SetLanguage(langID);
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))
1094 WORD Lang2 = GetLangFromLocale(LOCALE_USER_DEFAULT);
1095 if (Lang2 != Lang1 && SetLanguage(Lang2))
1097 WORD Lang3 = GetLangFromLocale(LOCALE_SYSTEM_DEFAULT);
1098 if (Lang3 != Lang2 && Lang3 != Lang1 && SetLanguage(Lang3))