summaryrefslogtreecommitdiff
path: root/src/mir_core/langpack.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mir_core/langpack.cpp')
-rw-r--r--src/mir_core/langpack.cpp631
1 files changed, 631 insertions, 0 deletions
diff --git a/src/mir_core/langpack.cpp b/src/mir_core/langpack.cpp
new file mode 100644
index 0000000000..015af8f469
--- /dev/null
+++ b/src/mir_core/langpack.cpp
@@ -0,0 +1,631 @@
+/*
+
+Miranda IM: the free IM client for Microsoft* Windows*
+
+Copyright 2000-2009 Miranda ICQ/IM 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<MUUID> lMuuids(10, CompareMuuids);
+static MUUID* pCurrentMuuid = NULL;
+
+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));
+
+ TCHAR szSearch[MAX_PATH];
+ PathToAbsoluteT(_T("langpack_*.txt"), szSearch, NULL);
+
+ WIN32_FIND_DATA fd;
+ HANDLE hFind = FindFirstFile(szSearch, &fd);
+ if (hFind != INVALID_HANDLE_VALUE) {
+ PathToAbsoluteT(fd.cFileName, szSearch, NULL);
+ 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();
+}