diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d1074554..bbcb5b630 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,12 +96,19 @@ endif() # until ready set(WITH_LOCALE_DEFAULT OFF) +if (MSVC OR ANDROID) + set(USE_MOMO_DEFAULT ON) +else() + set(USE_MOMO_DEFAULT OFF) +endif() + option(BUILD_GUI "Build the tracker (disable to build only a headless player)" ${BUILD_GUI_DEFAULT}) option(WITH_LOCALE "Use libintl for language support" ${WITH_LOCALE_DEFAULT}) option(USE_RTMIDI "Build with MIDI support using RtMidi." ${USE_RTMIDI_DEFAULT}) option(USE_SDL2 "Build with SDL2. Required to build with GUI." ${USE_SDL2_DEFAULT}) option(USE_SNDFILE "Build with libsndfile. Required in order to work with audio files." ${USE_SNDFILE_DEFAULT}) option(USE_BACKWARD "Use backward-cpp to print a backtrace on crash/abort." ${USE_BACKWARD_DEFAULT}) +option(USE_MOMO "Build a libintl implementation instead of using the system one." ${USE_MOMO_DEFAULT}) option(WITH_JACK "Whether to build with JACK support. Auto-detects if JACK is available" ${WITH_JACK_DEFAULT}) option(WITH_PORTAUDIO "Whether to build with PortAudio for audio output." ${WITH_PORTAUDIO_DEFAULT}) option(WITH_RENDER_SDL "Whether to build with the SDL_Renderer render backend." ${WITH_RENDER_SDL_DEFAULT}) @@ -176,17 +183,26 @@ if (WIN32) endif() if (WITH_LOCALE) - if ("${CMAKE_VERSION}" VERSION_LESS "3.2") - message(FATAL_ERROR "CMake 3.2 or later required for locale support.") - else() - include(FindIntl) - if (NOT Intl_FOUND) - message(FATAL_ERROR "Could not find libintl!") - endif() + if (USE_MOMO) + add_library(momo STATIC src/momo/momo.c) list(APPEND DEPENDENCIES_DEFINES HAVE_LOCALE) - list(APPEND DEPENDENCIES_INCLUDE_DIRS ${Intl_INCLUDE_DIRS}) - list(APPEND DEPENDENCIES_LIBRARIES ${Intl_LIBRARIES}) - message(STATUS "Using libintl") + list(APPEND DEPENDENCIES_DEFINES HAVE_MOMO) + list(APPEND DEPENDENCIES_INCLUDE_DIRS src/momo) + list(APPEND DEPENDENCIES_LIBRARIES momo) + message(STATUS "Using libintl (Momo)") + else() + if ("${CMAKE_VERSION}" VERSION_LESS "3.2") + message(FATAL_ERROR "CMake 3.2 or later required for locale support.") + else() + include(FindIntl) + if (NOT Intl_FOUND) + message(FATAL_ERROR "Could not find libintl! Try enabling USE_MOMO.") + endif() + list(APPEND DEPENDENCIES_DEFINES HAVE_LOCALE) + list(APPEND DEPENDENCIES_INCLUDE_DIRS ${Intl_INCLUDE_DIRS}) + list(APPEND DEPENDENCIES_LIBRARIES ${Intl_LIBRARIES}) + message(STATUS "Using libintl (system)") + endif() endif() endif() diff --git a/src/gui/gui.h b/src/gui/gui.h index e6f98e506..0f35a3748 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -131,6 +131,10 @@ enum FurnaceGUIRenderBackend { #define GUI_DECORATIONS_DEFAULT 1 #endif +#ifdef HAVE_MOMO +#define ngettext momo_ngettext +#endif + // TODO: // - add colors for FM envelope and waveform // - maybe add "alternate" color for FM modulators/carriers (a bit difficult) diff --git a/src/main.cpp b/src/main.cpp index d9a30e60e..e24d7a147 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -48,6 +48,16 @@ struct sigaction termsa; #define TUT_INTRO_PLAYED false #endif +#ifdef HAVE_MOMO +#define TA_SETLOCALE momo_setlocale +#define TA_BINDTEXTDOMAIN momo_bindtextdomain +#define TA_TEXTDOMAIN momo_textdomain +#else +#define TA_SETLOCALE setlocale +#define TA_BINDTEXTDOMAIN bindtextdomain +#define TA_TEXTDOMAIN textdomain +#endif + #include "cli/cli.h" #ifdef HAVE_GUI @@ -491,22 +501,22 @@ int main(int argc, char** argv) { #ifdef HAVE_LOCALE const char* localeRet=NULL; - if ((localeRet=setlocale(LC_CTYPE,""))==NULL) { + if ((localeRet=TA_SETLOCALE(LC_CTYPE,""))==NULL) { logE("could not set locale (CTYPE)!"); } else { logV("locale: %s",localeRet); } - if ((localeRet=setlocale(LC_MESSAGES,""))==NULL) { + if ((localeRet=TA_SETLOCALE(LC_MESSAGES,""))==NULL) { logE("could not set locale (MESSAGES)!"); } else { logV("locale: %s",localeRet); } - if ((localeRet=bindtextdomain("furnace","locale"))==NULL) { + if ((localeRet=TA_BINDTEXTDOMAIN("furnace","locale"))==NULL) { logE("could not bind text domain!"); } else { logV("text domain 1: %s",localeRet); } - if ((localeRet=textdomain("furnace"))==NULL) { + if ((localeRet=TA_TEXTDOMAIN("furnace"))==NULL) { logE("could not text domain!"); } else { logV("text domain 2: %s",localeRet); diff --git a/src/momo/momo.c b/src/momo/momo.c new file mode 100644 index 000000000..5125f57cc --- /dev/null +++ b/src/momo/momo.c @@ -0,0 +1,351 @@ +/* Momo - portable gettext() implementation + * Copyright (C) 2024 tildearrow + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include "libintl.h" + +static char curLocale[64]; +static char tempPath[4096]; + +struct LocaleDomain { + char path[4096]; + char name[64]; + unsigned char* mo; + size_t moLen; + const char** stringPtr; + const char** transPtr; + size_t stringCount; +}; + +struct MOHeader { + unsigned int magic; + unsigned int version; + unsigned int stringCount; + unsigned int stringPtr; + unsigned int transPtr; + unsigned int hashSize; + unsigned int hashPtr; +}; + +static struct LocaleDomain* curDomain=NULL; +static struct LocaleDomain** domains=NULL; +static size_t domainsLen=0; + +// utility + +unsigned char domainsInsert(struct LocaleDomain* item) { + struct LocaleDomain** newDomains=malloc(sizeof(struct LocaleDomain*)*(domainsLen+1)); + if (newDomains==NULL) return 0; + if (domains!=NULL) { + memcpy(newDomains,domains,sizeof(struct LocaleDomain*)*domainsLen); + free(domains); + } + domains=newDomains; + domains[domainsLen++]=item; + return 1; +} + +unsigned char domainsRemove(struct LocaleDomain* item) { + if (domains==NULL) return 0; + unsigned char found=0; + for (size_t i=0; iname,domainName)==0) { + newDomain=domains[i]; + found=1; + break; + } + } + } + if (newDomain==NULL) { + // create new domain + newDomain=malloc(sizeof(struct LocaleDomain)); + if (newDomain==NULL) { + errno=ENOMEM; + return NULL; + } + memset(newDomain,0,sizeof(struct LocaleDomain)); + } + strncpy(newDomain->name,domainName,64); + if (dirName==NULL) { + if (!found) { + free(newDomain); + return NULL; + } + return newDomain->path; + } else { + strncpy(newDomain->path,dirName,4096); + } + + // load domain + if (newDomain->mo==NULL) { + snprintf(tempPath,4096,"%s/%s/LC_MESSAGES/%s.mo",newDomain->path,curLocale,newDomain->name); + + FILE* f=fopen(tempPath,"rb"); + if (f==NULL) { + // try without country + char* cPos=strchr(curLocale,'_'); + if (cPos) { + *cPos=0; + } + snprintf(tempPath,4096,"%s/%s/LC_MESSAGES/%s.mo",newDomain->path,curLocale,newDomain->name); + f=fopen(tempPath,"rb"); + if (f==NULL) { + // give up + if (found) { + if (newDomain==curDomain) curDomain=NULL; + domainsRemove(newDomain); + } + free(newDomain); + return NULL; + } + } + + if (fseek(f,0,SEEK_END)!=0) { + // give up + fclose(f); + if (found) { + if (newDomain==curDomain) curDomain=NULL; + domainsRemove(newDomain); + } + free(newDomain); + return NULL; + } + + long moSize=ftell(f); + if (moSizemoLen=moSize; + + if (fseek(f,0,SEEK_SET)!=0) { + // give up + fclose(f); + if (found) { + if (newDomain==curDomain) curDomain=NULL; + domainsRemove(newDomain); + } + free(newDomain); + return NULL; + } + + // allocate + newDomain->mo=malloc(newDomain->moLen); + if (newDomain->mo==NULL) { + // give up + fclose(f); + if (found) { + if (newDomain==curDomain) curDomain=NULL; + domainsRemove(newDomain); + } + free(newDomain); + errno=ENOMEM; + return NULL; + } + memset(newDomain->mo,0,newDomain->moLen); + + // read + if (fread(newDomain->mo,1,newDomain->moLen,f)!=newDomain->moLen) { + // give up + free(newDomain->mo); + fclose(f); + if (found) { + if (newDomain==curDomain) curDomain=NULL; + domainsRemove(newDomain); + } + free(newDomain); + return NULL; + } + fclose(f); + + // parse + struct MOHeader* header=(struct MOHeader*)newDomain->mo; + if (header->magic!=0x950412de) { + // give up + free(newDomain->mo); + if (found) { + if (newDomain==curDomain) curDomain=NULL; + domainsRemove(newDomain); + } + free(newDomain); + return NULL; + } + + if (header->stringPtr+(header->stringCount*8)>newDomain->moLen || + header->transPtr+(header->stringCount*8)>newDomain->moLen || + header->hashPtr+(header->hashSize*4)>newDomain->moLen) { + // give up + free(newDomain->mo); + if (found) { + if (newDomain==curDomain) curDomain=NULL; + domainsRemove(newDomain); + } + free(newDomain); + return NULL; + } + + newDomain->stringCount=header->stringCount; + if (newDomain->stringCount) { + newDomain->stringPtr=malloc(newDomain->stringCount*sizeof(const char*)); + newDomain->transPtr=malloc(newDomain->stringCount*sizeof(const char*)); + } + + unsigned int* strTable=(unsigned int*)(&newDomain->mo[header->stringPtr]); + unsigned int* transTable=(unsigned int*)(&newDomain->mo[header->transPtr]); + + for (size_t i=0; istringCount; i++) { + newDomain->stringPtr[i]=(const char*)(&newDomain->mo[strTable[1+(i<<1)]]); + newDomain->transPtr[i]=(const char*)(&newDomain->mo[transTable[1+(i<<1)]]); + } + } + + // add to domain list + if (!found) { + if (!domainsInsert(newDomain)) { + if (newDomain->mo) free(newDomain->mo); + free(newDomain); + errno=ENOMEM; + return NULL; + } + } + return newDomain->path; +} + +const char* momo_textdomain(const char* domainName) { + if (strcmp(curLocale,"C")==0) return domainName; + if (strcmp(curLocale,"POSIX")==0) return domainName; + if (strcmp(curLocale,"en")==0) return domainName; + if (strcmp(curLocale,"en_US")==0) return domainName; + + if (domainName==NULL) { + if (curDomain==NULL) return NULL; + return curDomain->name; + } + // set the domain + if (domains==NULL) return NULL; + for (size_t i=0; iname,domainName)==0) { + curDomain=domains[i]; + return curDomain->name; + } + } + return NULL; +} + +const char* momo_gettext(const char* str) { + if (curDomain==NULL) { + return str; + } + // TODO: optimize + for (size_t i=0; istringCount; i++) { + if (strcmp(curDomain->stringPtr[i],str)==0) return curDomain->transPtr[i]; + } + return str; +} + +const char* momo_ngettext(const char* str1, const char* str2, unsigned long amount) { + if (curDomain==NULL) { + if (amount==1) return str1; + return str2; + } + // TODO: implement + return str1; +} diff --git a/src/momo/momo.h b/src/momo/momo.h new file mode 100644 index 000000000..3c64098bd --- /dev/null +++ b/src/momo/momo.h @@ -0,0 +1,45 @@ +/* Momo - portable gettext() implementation + * Copyright (C) 2024 tildearrow + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +const char* momo_setlocale(int type, const char* locale); +const char* momo_bindtextdomain(const char* domainName, const char* dirName); +const char* momo_textdomain(const char* domainName); + +const char* momo_gettext(const char* str); +const char* momo_ngettext(const char* str1, const char* str2, unsigned long amount); + +#ifdef __cplusplus +} +#endif + +#ifdef MOMO_LIBINTL +#define setlocale momo_setlocale +#define bindtextdomain momo_bindtextdomain +#define textdomain momo_textdomain + +#define gettext momo_gettext +#define ngettext momo_ngettext +#endif diff --git a/src/ta-utils.h b/src/ta-utils.h index 8c18c31ea..aecab8d3c 100644 --- a/src/ta-utils.h +++ b/src/ta-utils.h @@ -43,8 +43,13 @@ typedef std::string String; #define CLAMP(x,xMin,xMax) (MIN(MAX((x),(xMin)),(xMax))) #ifdef HAVE_LOCALE +#ifdef HAVE_MOMO +#include +#define _(_str) momo_gettext(_str) +#else #include #define _(_str) gettext(_str) +#endif #else #define _(_str) _str #endif