2 * @file LanguageSelect.cpp
4 * @brief Implements the Language Selection dialog class (which contains the language data)
8 #include "LanguageSelect.h"
10 // Escaped character constants in range 0x80-0xFF are interpreted in current codepage
11 // Using C locale gets us direct mapping to Unicode codepoints
12 #pragma setlocale("C")
18 static wchar_t *EatPrefix(wchar_t *text, const wchar_t *prefix);
19 static void unslash(std::wstring &s);
20 static HANDLE NTAPI FindFile(HANDLE h, LPCTSTR path, WIN32_FIND_DATA *fd);
23 * @brief A class holding information about language file.
28 LANGID id; /**< Language ID. */
30 static LANGID LangId(const char *lang, const char *sublang);
33 * A constructor taking a language id as parameter.
34 * @param [in] id Language ID to use.
36 explicit LangFileInfo(LANGID id): id(id) { };
38 explicit LangFileInfo(LPCTSTR path);
39 std::wstring GetString(LCTYPE type) const;
47 static const struct rg rg[];
51 * @brief An array holding language IDs and names.
53 const struct LangFileInfo::rg LangFileInfo::rg[] =
56 LANG_AFRIKAANS, "AFRIKAANS\0"
59 LANG_ALBANIAN, "ALBANIAN\0"
62 LANG_ARABIC, "ARABIC\0" "SAUDI_ARABIA\0"
80 LANG_ARMENIAN, "ARMENIAN\0"
83 LANG_ASSAMESE, "ASSAMESE\0"
86 LANG_AZERI, "AZERI\0" "LATIN\0"
90 LANG_BASQUE, "BASQUE\0"
93 LANG_BELARUSIAN, "BELARUSIAN\0"
96 LANG_BENGALI, "BENGALI\0"
99 LANG_BULGARIAN, "BULGARIAN\0"
102 LANG_CATALAN, "CATALAN\0"
105 LANG_CHINESE, "CHINESE\0" "TRADITIONAL\0"
112 LANG_CROATIAN, "CROATIAN\0"
115 LANG_CZECH, "CZECH\0"
118 LANG_DANISH, "DANISH\0"
121 LANG_DIVEHI, "DIVEHI\0"
124 MAKELANGID(LANG_DUTCH, SUBLANG_DUTCH), "DUTCH\0"
128 LANG_ENGLISH, "ENGLISH\0" "US\0"
143 LANG_ESTONIAN, "ESTONIAN\0"
146 LANG_FAEROESE, "FAEROESE\0"
149 LANG_FARSI, "FARSI\0"
152 LANG_FINNISH, "FINNISH\0"
155 MAKELANGID(LANG_FRENCH, SUBLANG_FRENCH), "FRENCH\0"
163 LANG_GALICIAN, "GALICIAN\0"
166 LANG_GEORGIAN, "GEORGIAN\0"
169 MAKELANGID(LANG_GERMAN, SUBLANG_GERMAN), "GERMAN\0"
176 LANG_GREEK, "GREEK\0"
179 LANG_GUJARATI, "GUJARATI\0"
182 LANG_HEBREW, "HEBREW\0"
185 LANG_HINDI, "HINDI\0"
188 LANG_HUNGARIAN, "HUNGARIAN\0"
191 LANG_ICELANDIC, "ICELANDIC\0"
194 LANG_INDONESIAN, "INDONESIAN\0"
197 MAKELANGID(LANG_ITALIAN, SUBLANG_ITALIAN), "ITALIAN\0"
201 LANG_JAPANESE, "JAPANESE\0"
204 LANG_KANNADA, "KANNADA\0"
207 MAKELANGID(LANG_KASHMIRI, SUBLANG_DEFAULT), "KASHMIRI\0"
211 LANG_KAZAK, "KAZAK\0"
214 LANG_KONKANI, "KONKANI\0"
217 MAKELANGID(LANG_KOREAN, SUBLANG_KOREAN), "KOREAN\0"
220 LANG_KYRGYZ, "KYRGYZ\0"
223 LANG_LATVIAN, "LATVIAN\0"
226 MAKELANGID(LANG_LITHUANIAN, SUBLANG_LITHUANIAN), "LITHUANIAN\0"
229 LANG_MACEDONIAN, "MACEDONIAN\0"
232 LANG_MALAY, "MALAY\0" "MALAYSIA\0"
233 "BRUNEI_DARUSSALAM\0"
236 LANG_MALAYALAM, "MALAYALAM\0"
239 LANG_MANIPURI, "MANIPURI\0"
242 LANG_MARATHI, "MARATHI\0"
245 LANG_MONGOLIAN, "MONGOLIAN\0"
248 MAKELANGID(LANG_NEPALI, SUBLANG_DEFAULT), "NEPALI\0"
252 LANG_NORWEGIAN, "NORWEGIAN\0" "BOKMAL\0"
256 LANG_ORIYA, "ORIYA\0"
259 LANG_POLISH, "POLISH\0"
262 MAKELANGID(LANG_PORTUGUESE, SUBLANG_PORTUGUESE), "PORTUGUESE\0"
266 LANG_PUNJABI, "PUNJABI\0"
269 LANG_ROMANIAN, "ROMANIAN\0"
272 LANG_RUSSIAN, "RUSSIAN\0"
275 LANG_SANSKRIT, "SANSKRIT\0"
278 MAKELANGID(LANG_SERBIAN, SUBLANG_DEFAULT), "SERBIAN\0"
283 LANG_SINDHI, "SINDHI\0"
286 LANG_SINHALESE, "SINHALESE\0"
289 LANG_SLOVAK, "SLOVAK\0"
292 LANG_SLOVENIAN, "SLOVENIAN\0"
295 MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH), "SPANISH\0"
301 "DOMINICAN_REPUBLIC\0"
317 LANG_SWAHILI, "SWAHILI\0"
320 MAKELANGID(LANG_SWEDISH, SUBLANG_SWEDISH), "SWEDISH\0"
324 LANG_SYRIAC, "SYRIAC\0"
327 LANG_TAMIL, "TAMIL\0"
330 LANG_TATAR, "TATAR\0"
333 LANG_TELUGU, "TELUGU\0"
339 LANG_TURKISH, "TURKISH\0"
342 LANG_UKRAINIAN, "UKRAINIAN\0"
345 LANG_URDU, "URDU\0" "PAKISTAN\0"
349 LANG_UZBEK, "UZBEK\0" "LATIN\0"
353 LANG_VIETNAMESE, "VIETNAMESE\0"
358 * @brief Get a language ID for given language + sublanguage.
359 * @param [in] lang Language name.
360 * @param [in] sublang Sub language name.
361 * @return Language ID.
363 LANGID LangFileInfo::LangId(const char *lang, const char *sublang)
365 // binary search the array for passed in lang
367 size_t upper = std::size(rg);
368 while (lower < upper)
370 size_t match = (upper + lower) >> 1;
371 int cmp = strcmp(rg[match].lang, lang);
379 LANGID baseid = rg[upper].id;
380 // figure out sublang
381 if ((baseid & ~0x3ff) && *sublang == '\0')
383 LANGID id = PRIMARYLANGID(baseid);
384 if (0 == strcmp(sublang, "DEFAULT"))
385 return MAKELANGID(id, SUBLANG_DEFAULT);
386 const char *sub = rg[upper].lang;
391 id += MAKELANGID(0, 1);
392 } while (id == baseid);
393 sub += strlen(sub) + 1;
394 if (0 == strcmp(sublang, sub))
401 * @brief A constructor taking a path to language file as parameter.
402 * @param [in] path Full path to the language file.
404 LangFileInfo::LangFileInfo(LPCTSTR path)
408 if (_tfopen_s(&f, path, _T("r,ccs=utf-8")) == 0 && f)
410 wchar_t buf[1024 + 1];
411 while (fgetws(buf, static_cast<int>(std::size(buf)) - 1, f) != nullptr)
415 swscanf_s(buf, L"msgid \" LANG_ENGLISH , SUBLANG_ENGLISH_US \" %d", &i);
418 if (fgetws(buf, static_cast<int>(std::size(buf)), f) != nullptr)
420 wchar_t *lang = wcsstr(buf, L"LANG_");
421 wchar_t *sublang = wcsstr(buf, L"SUBLANG_");
422 wchar_t *langNext = nullptr;
423 wchar_t *sublangNext = nullptr;
426 wcstok_s(lang, L",\" \t\r\n", &langNext);
427 wcstok_s(sublang, L",\" \t\r\n", &sublangNext);
428 lang += std::size("LANG");
429 sublang += std::size("SUBLANG");
430 if (0 != wcscmp(sublang, L"DEFAULT"))
432 sublang = EatPrefix(sublang, lang);
433 if (sublang && *sublang)
434 sublang = EatPrefix(sublang, L"_");
439 id = LangId(W2A(lang), W2A(sublang));
450 std::wstring 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 CLanguageSelect::CLanguageSelect()
484 * @brief Remove prefix from the string.
485 * @param [in] text String from which to jump over prefix.
486 * @param [in] prefix Prefix string to jump over.
487 * @return String without the prefix.
488 * @note Function returns pointer to original string,
489 * it does not allocate a new string.
491 static wchar_t *EatPrefix(wchar_t *text, const wchar_t *prefix)
493 if (size_t len = wcslen(prefix))
494 if (_memicmp(text, prefix, len * sizeof(wchar_t)) == 0)
500 * @brief Convert C style \\nnn, \\r, \\n, \\t etc into their indicated characters.
501 * @param [in,out] s String to convert.
503 static void unslash(std::wstring &s)
505 wchar_t *p = &*s.begin();
538 *p = (wchar_t)wcstol(r, &q, 16);
541 *p = (wchar_t)wcstol(r - 1, &q, 8);
553 s.resize(p - 1 - &*s.begin());
557 * @brief Load language.file
558 * @param [in] wLangId
559 * @return `true` on success, `false` otherwise.
561 bool CLanguageSelect::LoadLanguageFile(LANGID wLangId, const std::wstring& sLanguagesFolder)
564 m_map_msgid_to_msgstr.clear();
566 std::wstring strPath = GetFileName(wLangId, sLanguagesFolder);
571 std::wstring *ps = nullptr;
572 std::wstring msgctxt;
575 if (_tfopen_s(&f, strPath.c_str(), _T("r,ccs=UTF-8")) != 0)
580 std::wstring directive;
581 auto addToMap = [&]() {
583 if (!msgctxt.empty())
593 m_map_msgid_to_msgstr.insert_or_assign(msgid, msgstr);
595 m_map_msgid_to_msgstr.insert_or_assign(L"\x01\"" + msgctxt + L"\"" + msgid, msgstr);
601 while (fgetws(buf, static_cast<int>(std::size(buf)), f) != nullptr)
603 if (wchar_t *p1 = EatPrefix(buf, L"#,"))
606 format.erase(0, format.find_first_not_of(L" \t\r\n"));
607 format.erase(format.find_last_not_of(L" \t\r\n") + 1);
609 else if (wchar_t *p2 = EatPrefix(buf, L"#."))
612 directive.erase(0, directive.find_first_not_of(L" \t\r\n"));
613 directive.erase(directive.find_last_not_of(L" \t\r\n") + 1);
615 else if (EatPrefix(buf, L"msgctxt "))
619 else if (EatPrefix(buf, L"msgid "))
623 else if (EatPrefix(buf, L"msgstr "))
629 wchar_t *p = wcschr(buf, '"');
630 wchar_t *q = wcsrchr(buf, '"');
631 if (std::wstring::size_type n = q - p)
633 ps->append(p + 1, n - 1);
649 * @brief Get a language file for the specified language ID.
650 * This function gets a language file name for the given language ID. Language
651 * files are currently named as [languagename].po.
652 * @param [in] wLangId Language ID.
653 * @return Language filename, or empty string if no file for language found.
655 std::wstring CLanguageSelect::GetFileName(LANGID wLangId, const std::wstring& sLanguagesFolder)
657 std::wstring filename;
658 std::wstring path = sLanguagesFolder;
659 if (!path.empty() && path.back() != '\\')
661 std::wstring pattern = path + L"*.po";
663 HANDLE h = INVALID_HANDLE_VALUE;
664 while ((h = FindFile(h, pattern.c_str(), &ff)) != INVALID_HANDLE_VALUE)
666 filename = path + ff.cFileName;
667 LangFileInfo lfi(filename.c_str());
668 if (lfi.id == wLangId)
669 ff.dwFileAttributes = INVALID_FILE_ATTRIBUTES; // terminate loop
676 /////////////////////////////////////////////////////////////////////////////
677 // CLanguageSelect commands
679 bool CLanguageSelect::TranslateString(const std::wstring& msgid, std::wstring &s) const
681 if (m_map_msgid_to_msgstr.find(msgid) != m_map_msgid_to_msgstr.end())
683 s = m_map_msgid_to_msgstr.at(msgid);
686 if (msgid.length() > 2 && msgid[0] == '\x01' && msgid[1] == '"')
688 size_t pos = msgid.find('"', 2);
689 if (pos != std::wstring::npos)
691 s = msgid.substr(pos + 1);