/* Miranda IM: the free IM client for Microsoft* Windows* Copyright 2000-12 Miranda IM, 2012-13 Miranda NG project, all portions of this codebase are copyrighted to the people listed in contributors.txt. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "commonheaders.h" #define LANGPACK_BUF_SIZE 4000 static int CompareMuuids(const MUUID* p1, const MUUID* p2) { return memcmp(p1, p2, sizeof(MUUID)); } static LIST lMuuids(10, CompareMuuids); static MUUID* pCurrentMuuid = NULL; static HANDLE hevChanged = 0; static BOOL bModuleInitialized = FALSE; struct LangPackEntry { DWORD englishHash; char *local; wchar_t *wlocal; MUUID* pMuuid; LangPackEntry* pNext; // for langpack items with the same hash value }; struct LangPackStruct { TCHAR filename[MAX_PATH]; TCHAR filePath[MAX_PATH]; char language[64]; char lastModifiedUsing[64]; char authors[256]; char authorEmail[128]; LangPackEntry *entry; int entryCount, entriesAlloced; LCID localeID; UINT defaultANSICp; } static langPack; static int IsEmpty(char *str) { int i = 0; while (str[i]) { if (str[i] != ' ' && str[i] != '\r' && str[i] != '\n') return 0; i++; } return 1; } static void ConvertBackslashes(char *str, UINT fileCp) { char *pstr; for (pstr = str; *pstr; pstr = CharNextExA(fileCp, pstr, 0)) { if (*pstr == '\\') { switch(pstr[1]) { case 'n': *pstr = '\n'; break; case 't': *pstr = '\t'; break; case 'r': *pstr = '\r'; break; default: *pstr = pstr[1]; break; } memmove(pstr+1, pstr+2, strlen(pstr+2) + 1); } } } #ifdef _DEBUG //#pragma optimize("gt", on) #endif // MurmurHash2 MIR_CORE_DLL(unsigned int) mir_hash(const void * key, unsigned int len) { // 'm' and 'r' are mixing constants generated offline. // They're not really 'magic', they just happen to work well. const unsigned int m = 0x5bd1e995; const int r = 24; // Initialize the hash to a 'random' value unsigned int h = len; // Mix 4 bytes at a time into the hash const unsigned char * data = (const unsigned char *)key; while (len >= 4) { unsigned int k = *(unsigned int *)data; k *= m; k ^= k >> r; k *= m; h *= m; h ^= k; data += 4; len -= 4; } // Handle the last few bytes of the input array switch(len) { case 3: h ^= data[2] << 16; case 2: h ^= data[1] << 8; case 1: h ^= data[0]; h *= m; }; // Do a few final mixes of the hash to ensure the last few // bytes are well-incorporated. h ^= h >> 13; h *= m; h ^= h >> 15; return h; } static unsigned int __fastcall hashstrW(const char * key) { if (key == NULL) return 0; const unsigned int len = (unsigned int)wcslen((const wchar_t*)key); char* buf = (char*)alloca(len + 1); for (unsigned i = 0; i <= len ; ++i) buf[i] = key[i << 1]; return mir_hash(buf, len); } static int SortLangPackHashesProc(LangPackEntry *arg1, LangPackEntry *arg2) { if (arg1->englishHash < arg2->englishHash) return -1; if (arg1->englishHash > arg2->englishHash) return 1; return (arg1->pMuuid < arg2->pMuuid) ? -1 : 1; } static void swapBytes(void* p, size_t iSize) { char *head = (char *)p; // here char *tail = head + iSize - 1; for (; tail > head; --tail, ++head) { char temp = *head; *head = *tail; *tail = temp; } } static bool EnterMuuid(const char* p, MUUID& result) { if (*p++ != '{') return false; BYTE* d = (BYTE*)&result; for (int nBytes = 0; *p && nBytes < 24; p++) { if (*p == '-') continue; if (*p == '}') break; if ( !isxdigit(*p)) return false; if ( !isxdigit(p[1])) return false; int c = 0; if (sscanf(p, "%2x", &c) != 1) return false; *d++ = (BYTE)c; nBytes++; p++; } if (*p != '}') return false; swapBytes(&result.a, sizeof(result.a)); swapBytes(&result.b, sizeof(result.b)); swapBytes(&result.c, sizeof(result.c)); return true; } static void LoadLangPackFile(FILE* fp, char* line, UINT fileCp) { while ( !feof(fp)) { if (fgets(line, LANGPACK_BUF_SIZE, fp) == NULL) break; if (IsEmpty(line) || line[0] == ';' || line[0] == 0) continue; rtrim(line); if (line[0] == '#') { strlwr(line); if ( !memcmp(line+1, "include", 7)) { TCHAR tszFileName[ MAX_PATH ]; TCHAR* fileName = mir_a2t(ltrim(line+9)); mir_sntprintf(tszFileName, SIZEOF(tszFileName), _T("%s%s"), langPack.filePath, fileName); mir_free(fileName); FILE* p = _tfopen(tszFileName, _T("r")); if (p) { line[0] = 0; fgets(line, SIZEOF(line), p); UINT fileCp = CP_ACP; if (strlen(line) >= 3 && line[0] == '\xef' && line[1] == '\xbb' && line[2] == '\xbf') { fileCp = CP_UTF8; fseek(p, 3, SEEK_SET); } else { fileCp = langPack.defaultANSICp; fseek(p, 0, SEEK_SET); } LoadLangPackFile(p, line, fileCp); fclose(p); } } else if ( !memcmp(line+1, "muuid", 5)) { MUUID t; if ( !EnterMuuid(line+7, t)) continue; MUUID* pNew = (MUUID*)mir_alloc(sizeof(MUUID)); memcpy(pNew, &t, sizeof(t)); lMuuids.insert(pNew); pCurrentMuuid = pNew; } continue; } ConvertBackslashes(line, fileCp); if (line[0] == '[' && line[ lstrlenA(line)-1 ] == ']') { if (langPack.entryCount && langPack.entry[ langPack.entryCount-1].local == NULL) langPack.entryCount--; char* pszLine = line+1; line[ lstrlenA(line)-1 ] = '\0'; if (++langPack.entryCount > langPack.entriesAlloced) { langPack.entriesAlloced += 128; langPack.entry = (LangPackEntry*)mir_realloc(langPack.entry, sizeof(LangPackEntry)*langPack.entriesAlloced); } LangPackEntry* E = &langPack.entry[ langPack.entryCount-1 ]; E->englishHash = mir_hashstr(pszLine); E->local = NULL; E->wlocal = NULL; E->pMuuid = pCurrentMuuid; E->pNext = NULL; continue; } if ( !langPack.entryCount) continue; LangPackEntry* E = &langPack.entry[ langPack.entryCount-1 ]; if (E->local == NULL) { E->local = mir_strdup(line); if (fileCp == CP_UTF8) Utf8DecodeCP(E->local, langPack.defaultANSICp, NULL); int iNeeded = MultiByteToWideChar(fileCp, 0, line, -1, 0, 0); E->wlocal = (wchar_t *)mir_alloc((iNeeded+1) * sizeof(wchar_t)); MultiByteToWideChar(fileCp, 0, line, -1, E->wlocal, iNeeded); } else { size_t iOldLenA = strlen(E->local); E->local = (char*)mir_realloc(E->local, iOldLenA + strlen(line) + 2); strcat(E->local, "\n"); strcat(E->local, line); if (fileCp == CP_UTF8) Utf8DecodeCP(E->local + iOldLenA + 1, langPack.defaultANSICp, NULL); int iNeeded = MultiByteToWideChar(fileCp, 0, line, -1, 0, 0); size_t iOldLen = wcslen(E->wlocal); E->wlocal = (wchar_t*)mir_realloc(E->wlocal, (sizeof(wchar_t) * (iOldLen + iNeeded + 2))); wcscat(E->wlocal, L"\n"); MultiByteToWideChar(fileCp, 0, line, -1, E->wlocal + iOldLen + 1, iNeeded); } } } MIR_CORE_DLL(int) LoadLangPack(const TCHAR *szLangPack) { int startOfLine=0; USHORT langID; lstrcpy(langPack.filename, szLangPack); lstrcpy(langPack.filePath, szLangPack); TCHAR* p = _tcsrchr(langPack.filePath, '\\'); if (p) p[1] = 0; FILE *fp = _tfopen(szLangPack, _T("rt")); if (fp == NULL) return 1; char line[ LANGPACK_BUF_SIZE ] = ""; fgets(line, SIZEOF(line), fp); UINT fileCp = CP_ACP; size_t lineLen = strlen(line); if (lineLen >= 3 && line[0] == '\xef' && line[1] == '\xbb' && line[2] == '\xbf') { fileCp = CP_UTF8; memmove(line, line + 3, lineLen - 2); } lrtrim(line); if (lstrcmpA(line, "Miranda Language Pack Version 1")) { fclose(fp); return 2; } //headers while ( !feof(fp)) { startOfLine = ftell(fp); if (fgets(line, SIZEOF(line), fp) == NULL) break; lrtrim(line); if (IsEmpty(line) || line[0] == ';' || line[0] == 0) continue; if (line[0] == '[' || line[0] == '#') break; char* pszColon = strchr(line, ':'); if (pszColon == NULL) { fclose(fp); return 3; } *pszColon++ = 0; if ( !lstrcmpA(line, "Language")) {mir_snprintf(langPack.language, sizeof(langPack.language), "%s", pszColon); lrtrim(langPack.language);} else if ( !lstrcmpA(line, "Last-Modified-Using")) {mir_snprintf(langPack.lastModifiedUsing, sizeof(langPack.lastModifiedUsing), "%s", pszColon); lrtrim(langPack.lastModifiedUsing);} else if ( !lstrcmpA(line, "Authors")) {mir_snprintf(langPack.authors, sizeof(langPack.authors), "%s", pszColon); lrtrim(langPack.authors);} else if ( !lstrcmpA(line, "Author-email")) {mir_snprintf(langPack.authorEmail, sizeof(langPack.authorEmail), "%s", pszColon); lrtrim(langPack.authorEmail);} else if ( !lstrcmpA(line, "Locale")) { char szBuf[20], *stopped; lrtrim(pszColon + 1); langID = (USHORT)strtol(pszColon, &stopped, 16); langPack.localeID = MAKELCID(langID, 0); GetLocaleInfoA(langPack.localeID, LOCALE_IDEFAULTANSICODEPAGE, szBuf, 10); szBuf[5] = 0; // codepages have max. 5 digits langPack.defaultANSICp = atoi(szBuf); if (fileCp == CP_ACP) fileCp = langPack.defaultANSICp; } } //body fseek(fp, startOfLine, SEEK_SET); langPack.entriesAlloced = 0; LoadLangPackFile(fp, line, fileCp); fclose(fp); pCurrentMuuid = NULL; qsort(langPack.entry, langPack.entryCount, sizeof(LangPackEntry), (int(*)(const void*, const void*))SortLangPackHashesProc); return 0; } ///////////////////////////////////////////////////////////////////////////////////////// static int SortLangPackHashesProc2(LangPackEntry *arg1, LangPackEntry *arg2) { if (arg1->englishHash < arg2->englishHash) return -1; if (arg1->englishHash > arg2->englishHash) return 1; return 0; } static char *LangPackTranslateString(MUUID* pUuid, const char *szEnglish, const int W) { if (langPack.entryCount == 0 || szEnglish == NULL) return (char*)szEnglish; LangPackEntry key, *entry; key.englishHash = W ? hashstrW(szEnglish) : mir_hashstr(szEnglish); entry = (LangPackEntry*)bsearch(&key, langPack.entry, langPack.entryCount, sizeof(LangPackEntry), (int(*)(const void*, const void*))SortLangPackHashesProc2); if (entry == NULL) return (char*)szEnglish; // try to find the exact match, otherwise the first entry will be returned if (pUuid) { for (LangPackEntry* p = entry->pNext; p != NULL; p = p->pNext) { if (p->pMuuid == pUuid) { entry = p; break; } } } return W ? (char *)entry->wlocal : entry->local; } MIR_CORE_DLL(int) Langpack_GetDefaultCodePage() { return langPack.defaultANSICp; } MIR_CORE_DLL(int) Langpack_GetDefaultLocale() { return (langPack.localeID == 0) ? LOCALE_USER_DEFAULT : langPack.localeID; } MIR_CORE_DLL(TCHAR*) Langpack_PcharToTchar(const char* pszStr) { if (pszStr == NULL) return NULL; int len = (int)strlen(pszStr); TCHAR* result = (TCHAR*)alloca((len+1)*sizeof(TCHAR)); MultiByteToWideChar(Langpack_GetDefaultCodePage(), 0, pszStr, -1, result, len); result[len] = 0; return mir_wstrdup(TranslateW(result)); } ///////////////////////////////////////////////////////////////////////////////////////// MIR_CORE_DLL(char*) TranslateA_LP(const char* str, int hLangpack) { return (char*)LangPackTranslateString(Langpack_LookupUuid(hLangpack), str, FALSE); } MIR_CORE_DLL(WCHAR*) TranslateW_LP(const WCHAR* str, int hLangpack) { return (WCHAR*)LangPackTranslateString(Langpack_LookupUuid(hLangpack), (LPCSTR)str, TRUE); } MIR_CORE_DLL(void) TranslateMenu_LP(HMENU hMenu, int hLangpack) { MUUID* uuid = Langpack_LookupUuid(hLangpack); MENUITEMINFO mii; mii.cbSize = MENUITEMINFO_V4_SIZE; for (int i = GetMenuItemCount(hMenu)-1; i >= 0; i--) { TCHAR str[256]; mii.fMask = MIIM_TYPE|MIIM_SUBMENU; mii.dwTypeData = (TCHAR*)str; mii.cch = SIZEOF(str); GetMenuItemInfo(hMenu, i, TRUE, &mii); if (mii.cch && mii.dwTypeData) { TCHAR* result = (TCHAR*)LangPackTranslateString(uuid, (const char*)mii.dwTypeData, TRUE); if (result != mii.dwTypeData) { mii.dwTypeData = result; mii.fMask = MIIM_TYPE; SetMenuItemInfo(hMenu, i, TRUE, &mii); } } if (mii.hSubMenu != NULL) TranslateMenu_LP(mii.hSubMenu, hLangpack); } } static void TranslateWindow(MUUID* pUuid, HWND hwnd) { TCHAR title[2048]; GetWindowText(hwnd, title, SIZEOF(title)); TCHAR* result = (TCHAR*)LangPackTranslateString(pUuid, (const char*)title, TRUE); if (result != title) SetWindowText(hwnd, result); } struct LANGPACKTRANSLATEDIALOG { HWND hwndDlg; int hLangpack; }; static BOOL CALLBACK TranslateDialogEnumProc(HWND hwnd, LPARAM lParam) { int hLangpack = (int)lParam; TCHAR szClass[32]; int id = GetDlgCtrlID(hwnd); MUUID* uuid = Langpack_LookupUuid(hLangpack); GetClassName(hwnd, szClass, SIZEOF(szClass)); if ( !lstrcmpi(szClass, _T("static")) || !lstrcmpi(szClass, _T("hyperlink")) || !lstrcmpi(szClass, _T("button")) || !lstrcmpi(szClass, _T("MButtonClass")) || !lstrcmpi(szClass, _T("MHeaderbarCtrl"))) TranslateWindow(uuid, hwnd); else if ( !lstrcmpi(szClass, _T("edit"))) { if (GetWindowLongPtr(hwnd, GWL_STYLE) & ES_READONLY) TranslateWindow(uuid, hwnd); } return TRUE; } MIR_CORE_DLL(void) TranslateDialog_LP(HWND hDlg, int hLangpack) { TranslateWindow( Langpack_LookupUuid(hLangpack), hDlg); EnumChildWindows(hDlg, TranslateDialogEnumProc, hLangpack); } ///////////////////////////////////////////////////////////////////////////////////////// MIR_CORE_DLL(MUUID*) Langpack_LookupUuid(WPARAM wParam) { int idx = (wParam >> 16) & 0xFFFF; return (idx > 0 && idx <= lMuuids.getCount()) ? lMuuids[ idx-1 ] : NULL; } MIR_CORE_DLL(int) Langpack_MarkPluginLoaded(PLUGININFOEX* pInfo) { int idx = lMuuids.getIndex(&pInfo->uuid); if (idx == -1) return 0; return (idx+1) << 16; } MIR_CORE_DLL(void) Langpack_SortDuplicates(void) { if (langPack.entryCount == 0) return; LangPackEntry *s = langPack.entry+1, *d = s, *pLast = langPack.entry; DWORD dwSavedHash = langPack.entry->englishHash; bool bSortNeeded = false; for (int i=1; i < langPack.entryCount; i++, s++) { if (s->englishHash != dwSavedHash) { pLast = d; if (s != d) *d++ = *s; else d++; dwSavedHash = s->englishHash; } else { bSortNeeded = true; LangPackEntry* p = (LangPackEntry*)mir_alloc(sizeof(LangPackEntry)); *p = *s; pLast->pNext = p; pLast = p; } } if (bSortNeeded) { langPack.entryCount = (int)(d - langPack.entry); qsort(langPack.entry, langPack.entryCount, sizeof(LangPackEntry), (int(*)(const void*, const void*))SortLangPackHashesProc); } } ///////////////////////////////////////////////////////////////////////////////////////// MIR_CORE_DLL(int) LoadLangPackModule(void) { bModuleInitialized = TRUE; ZeroMemory(&langPack, sizeof(langPack)); hevChanged = CreateHookableEvent(ME_LANGPACK_CHANGED); TCHAR szSearch[MAX_PATH]; PathToAbsoluteT(_T("langpack_*.txt"), szSearch); WIN32_FIND_DATA fd; HANDLE hFind = FindFirstFile(szSearch, &fd); if (hFind != INVALID_HANDLE_VALUE) { PathToAbsoluteT(fd.cFileName, szSearch); FindClose(hFind); LoadLangPack(szSearch); } return 0; } void UnloadLangPackModule() { if ( !bModuleInitialized) return; int i; for (i=0; i < lMuuids.getCount(); i++) mir_free(lMuuids[i]); lMuuids.destroy(); LangPackEntry* p = langPack.entry; for (i=0; i < langPack.entryCount; i++, p++) { if (p->pNext != NULL) { for (LangPackEntry* p1 = p->pNext; p1 != NULL;) { LangPackEntry* p2 = p1; p1 = p1->pNext; mir_free(p2->local); mir_free(p2->wlocal); mir_free(p2); } } mir_free(p->local); mir_free(p->wlocal); } if (langPack.entryCount) { mir_free(langPack.entry); langPack.entry=0; langPack.entryCount=0; } } ///////////////////////////////////////////////////////////////////////////////////////// MIR_CORE_DLL(void) ReloadLangpack(TCHAR *pszStr) { if (pszStr == NULL) pszStr = langPack.filename; UnloadLangPackModule(); LoadLangPack(pszStr); Langpack_SortDuplicates(); NotifyEventHooks(hevChanged, 0, 0); }