From f39e928b4b2d50371c29d23b6ba59ace79482858 Mon Sep 17 00:00:00 2001 From: Charlie Fenton Date: Wed, 12 Jun 2013 04:03:02 -0700 Subject: [PATCH] lib: Simplified string localization code adapted from wxWidgets. Allows translation using BOINC's locale files without the need to link code with wxWidgets. See comments in files for details. --- lib/translate.cpp | 309 ++++++++++++++++++++++++++++++++++++++++++++++ lib/translate.h | 65 ++++++++++ 2 files changed, 374 insertions(+) create mode 100644 lib/translate.cpp create mode 100644 lib/translate.h diff --git a/lib/translate.cpp b/lib/translate.cpp new file mode 100644 index 0000000000..761c8b3506 --- /dev/null +++ b/lib/translate.cpp @@ -0,0 +1,309 @@ +// This file is part of BOINC. +// http://boinc.berkeley.edu +// Copyright (C) 2013 University of California +// +// BOINC is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, +// either version 3 of the License, or (at your option) any later version. +// +// BOINC 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with BOINC. If not, see . + +// Simplified string localization code adapted from +// wxWidgets src/common/intl.cpp. +// Assumes all catalogs are encoded UTF-8 +// +// For sample code to get preferred system language, see +// wxLocale::GetSystemLanguage() in wxWidgets src/common/intl.cpp + +#include +#include +#include +#include +#include // for MAXPATHLEN +#include // For stat() + +#include "translate.h" + +static static const uint32_t MSGCATALOG_MAGIC = 0x950412de; +const uint32_t MSGCATALOG_MAGIC_SW = 0xde120495; + +#define MAXCATALOGS 5 + +// an entry in the string table +struct MsgTableEntry { + uint32_t nLen; // length of the string + uint32_t ofsString; // pointer to the string +}; + +// header of a .mo file +struct MsgCatalogHeader { + uint32_t magic, // offset +00: magic id + revision, // +04: revision + numStrings; // +08: number of strings in the file + uint32_t ofsOrigTable, // +0C: start of original string table + ofsTransTable; // +10: start of translated string table + uint32_t nHashSize, // +14: hash table size + ofsHashTable; // +18: offset of hash table start +}; + + +struct MsgCatalogData { + uint8_t *pData; // Pointer to catalog data + uint32_t nSize; // Amount of memory pointed to by pData. + uint32_t NumStrings; // number of strings in this domain + bool bSwapped; // wrong endianness? + MsgTableEntry *pOrigTable; // pointer to original strings + MsgTableEntry *pTransTable; // pointer to translated strings + bool bIsEnglish; // true if language code == "en" +}; + +static struct MsgCatalogData theCatalogData[MAXCATALOGS]; + +static uint32_t numLoadedCatalogs = 0; + +// swap the 2 halves of 32 bit integer if needed +static uint32_t Swap(uint32_t ui, bool bSwapped) { + return bSwapped ? (ui << 24) | ((ui & 0xff00) << 8) | + ((ui >> 8) & 0xff00) | (ui >> 24) + : ui; +} + + +// load a catalog from disk +// open disk file and read in it's contents +// +// catalogsDir is the path to the locale directory in +// the BOINC Data directory (ending in "locale/"). +// +// language code is the standad language code such +// as "fr" or "zh_CN". +// +// catalogName is the domain (the file name of the +// *.mo file without the .mo such as "BOINC-Manager"). +// +// Returns true if successful. +// +bool LoadCatalog(const char * catalogsDir, + const char *languageCode, + const char *catalogName + ) { + char searchPath[MAXPATHLEN]; + struct stat sbuf; + char temp[32]; + char *underscore; + FILE * f; + uint8_t *pData; + uint32_t nSize; + uint32_t NumStrings; + bool bSwapped; + MsgTableEntry *pOrigTable; + MsgTableEntry *pTransTable; + MsgCatalogData *pCatalog; + + if (!strcmp("en", languageCode)) { + theCatalogData[numLoadedCatalogs++].bIsEnglish = true; + return true; + } + + strlcpy(searchPath, catalogsDir, sizeof(searchPath)); + strlcat(searchPath, languageCode, sizeof(searchPath)); + strlcat(searchPath, "/", sizeof(searchPath)); + strlcat(searchPath, catalogName, sizeof(searchPath)); + strlcat(searchPath, ".mo", sizeof(searchPath)); + if (stat(searchPath, &sbuf) != 0) { + // Try just base locale name: for things like "fr_BE" (belgium + // french) we should use "fr" if no belgium specific message + // catalogs exist + strlcpy(temp, languageCode, sizeof(temp)); + underscore = strchr(temp, (int)'_'); + if (underscore == NULL) return false; + *underscore = '\0'; + strlcpy(searchPath, catalogsDir, sizeof(searchPath)); + strlcat(searchPath, temp, sizeof(searchPath)); + strlcat(searchPath, "/", sizeof(searchPath)); + strlcat(searchPath, catalogName, sizeof(searchPath)); + strlcat(searchPath, ".mo", sizeof(searchPath)); + if (stat(searchPath, &sbuf) != 0) return false; + } + + + pData = (uint8_t*)malloc(sbuf.st_size); + if (pData == NULL) return false; + f = fopen(searchPath, "r"); + if (f == NULL) { + free(pData); + pData = NULL; + return false; + } + nSize = fread(pData, 1, sbuf.st_size, f); + fclose(f); + if (nSize != sbuf.st_size) { + free(pData); + pData = NULL; + return false; + } + + // examine header + bool bValid = nSize + (size_t)0 > sizeof(MsgCatalogHeader); + + MsgCatalogHeader *pHeader = (MsgCatalogHeader *)pData; + if ( bValid ) { + // we'll have to swap all the integers if it's true + bSwapped = pHeader->magic == MSGCATALOG_MAGIC_SW; + + // check the magic number + bValid = bSwapped || pHeader->magic == MSGCATALOG_MAGIC; + } + + if ( !bValid ) { + // it's either too short or has incorrect magic number + free(pData); + pData = NULL; + return false; + } + + // initialize + NumStrings = Swap(pHeader->numStrings, bSwapped); + pOrigTable = (MsgTableEntry *)(pData + + Swap(pHeader->ofsOrigTable, bSwapped)); + pTransTable = (MsgTableEntry *)(pData + + Swap(pHeader->ofsTransTable, bSwapped)); + + + // now parse catalog's header and try to extract catalog charset + uint32_t ofsString = Swap(pOrigTable->ofsString, bSwapped); + // this check could fail for a corrupt message catalog + if ( ofsString + Swap(pOrigTable->nLen, bSwapped) <= nSize) { + char *begin = strstr((char *)pTransTable, + "Content-Type: text/plain; charset=" + ); + if (begin != NULL) { + if (strncasecmp(begin, "utf-8", 5)) { + fprintf(stderr, "File %s is not utf-8!\n", searchPath); + free(pData); + pData = NULL; + return false; + } + } + } + + pCatalog = &(theCatalogData[numLoadedCatalogs++]); + pCatalog->pData = pData; + pCatalog->nSize = nSize; + pCatalog->NumStrings = NumStrings; + pCatalog->bSwapped = bSwapped; + pCatalog->pOrigTable = pOrigTable; + pCatalog->pTransTable = pTransTable; + pCatalog->bIsEnglish = false; + + return true; +} + + +// Searches through the catalogs in the order they were added +// until it finds a translation for the src string, and +// returns a ponter to the UTF-8 encoded localized string. +// Returns a pointer to the original string if no translation +// was found. +// +uint8_t * _(char *src) { + int i, j; + MsgCatalogData *pCatalog; + + for (j=0; jbIsEnglish) { + return (uint8_t *)src; + } + + if (pCatalog->pData == NULL) continue; // Should never happen + for (i=0; i< pCatalog->NumStrings; ++i) { + if (!strcmp((char *)pCatalog->pData + pCatalog->pOrigTable[i].ofsString, src)) { + return (pCatalog->pData + pCatalog->pTransTable[i].ofsString); + } + } + } + + return (uint8_t *)src; +} + + +// catalogsDir is the path to the locale directory in +// the BOINC Data directory (ending in "locale/"). +// +// language code is the standad language code such +// as "fr" or "zh_CN". +// +// catalogName is the domain (the file name of the +// *.mo file without the .mo such as "BOINC-Manager"). +// +// Returns true if successful. +// +bool BOINCTranslationAddCatalog(const char * catalogsDir, + const char *languageCode, + const char * catalogName + ) { + if (numLoadedCatalogs >= (MAXCATALOGS)) { + fprintf(stderr, "Trying to load too many catalogs\n"); + return false; + } + + if (!LoadCatalog(catalogsDir, languageCode, catalogName)) { + fprintf(stderr, + "could not load catalog %s for langage %s\n", + catalogName, languageCode + ); + return false; + } + + return true; +} + + +void BOINCTranslationInit() { + int i; + MsgCatalogData *pCatalog; + + numLoadedCatalogs = 0; + + for (i=0; ipData = NULL; + pCatalog->nSize = 0; + pCatalog->NumStrings = 0; + pCatalog->bSwapped = false; + pCatalog->pOrigTable = NULL; + pCatalog->pTransTable = NULL; + pCatalog->bIsEnglish = false; + } +} + + +void BOINCTranslationCleanup() { + int i; + MsgCatalogData *pCatalog; + + for (i=0; ipData) free(pCatalog->pData); + pCatalog->pData = NULL; + pCatalog->nSize = 0; + pCatalog->NumStrings = 0; + pCatalog->bSwapped = false; + pCatalog->pOrigTable = NULL; + pCatalog->pTransTable = NULL; + pCatalog->bIsEnglish = false; + } + + numLoadedCatalogs = 0; +} diff --git a/lib/translate.h b/lib/translate.h new file mode 100644 index 0000000000..d866b76edc --- /dev/null +++ b/lib/translate.h @@ -0,0 +1,65 @@ +// This file is part of BOINC. +// http://boinc.berkeley.edu +// Copyright (C) 2013 University of California +// +// BOINC is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, +// either version 3 of the License, or (at your option) any later version. +// +// BOINC 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with BOINC. If not, see . + +// Simplified string localization code adapted from +// wxWidgets src/common/intl.cpp. +// Assumes all catalogs are encoded UTF-8 +// +// For sample code to get preferred system language, see +// wxLocale::GetSystemLanguage() in wxWidgets src/common/intl.cpp + +#ifndef _BOINC_TRANSLATE_H +#define _BOINC_TRANSLATE_H + +// We use only C APIs for maximum portability +#ifdef __cplusplus +extern "C" { +#endif + +void BOINCTranslationInit(); + +// catalogsDir is the path to the locale directory in +// the BOINC Data directory (ending in "locale/"). +// +// language code is the standad language code such +// as "fr" or "zh_CN". +// +// catalogName is the domain (the file name of the +// *.mo file without the .mo such as "BOINC-Manager"). +// +// Returns true if successful. +// +bool BOINCTranslationAddCatalog(const char * catalogsDir, + const char *languageCode, + const char * catalogName + ); + +// Searches through the catalogs in the order they were added +// until it finds a translation for the src string, and +// returns a ponter to the UTF-8 encoded localized string. +// Returns a pointer to the original string if no translation +// was found. +// +uint8_t * _(char *src); + +void BOINCTranslationCleanup(); + +#ifdef __cplusplus +} // extern "C" { +#endif + +#endif // _BOINC_TRANSLATE_H