libintl for systems without libintl

warning: incomplete!
This commit is contained in:
tildearrow 2024-05-30 19:34:34 -05:00
parent e93f888abd
commit 145212171f
6 changed files with 445 additions and 14 deletions

View file

@ -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()

View file

@ -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)

View file

@ -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);

351
src/momo/momo.c Normal file
View file

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#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; i<domainsLen; i++) {
if (domains[i]==item) {
found=1;
break;
}
}
if (!found) return 0;
if (domainsLen==1) {
domainsLen=0;
free(domains);
domains=NULL;
return 1;
}
struct LocaleDomain** newDomains=malloc(sizeof(struct LocaleDomain*)*(domainsLen-1));
if (newDomains==NULL) return 0;
size_t d=0;
found=0;
for (size_t i=0; i<domainsLen; i++) {
if (domains[i]!=item || found) {
newDomains[d++]=domains[i];
} else {
found=1;
}
}
domainsLen--;
free(domains);
domains=newDomains;
return 1;
}
// implementation
const char* momo_setlocale(int type, const char* locale) {
if (locale==NULL) {
return curLocale;
}
if (locale[0]==0) {
// get the locale from environment
locale=getenv("LC_ALL");
if (locale==NULL) {
locale=getenv("LC_MESSAGES");
if (locale==NULL) {
locale=getenv("LANG");
if (locale==NULL) {
locale="C";
}
}
}
}
strncpy(curLocale,locale,64);
// cut anything after the dot (we only support UTF-8)
char* dotPos=strchr(curLocale,'.');
if (dotPos) {
*dotPos=0;
}
return curLocale;
}
const char* momo_bindtextdomain(const char* domainName, const char* dirName) {
if (strcmp(curLocale,"C")==0) return dirName;
if (strcmp(curLocale,"POSIX")==0) return dirName;
if (strcmp(curLocale,"en")==0) return dirName;
if (strcmp(curLocale,"en_US")==0) return dirName;
struct LocaleDomain* newDomain=NULL;
unsigned char found=0;
if (domains!=NULL) {
// search for domain
for (size_t i=0; i<domainsLen; i++) {
if (strcmp(domains[i]->name,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 (moSize<sizeof(struct MOHeader)) {
// give up
fclose(f);
if (found) {
if (newDomain==curDomain) curDomain=NULL;
domainsRemove(newDomain);
}
free(newDomain);
return NULL;
}
newDomain->moLen=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; i<newDomain->stringCount; 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; i<domainsLen; i++) {
if (strcmp(domains[i]->name,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; i<curDomain->stringCount; 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;
}

45
src/momo/momo.h Normal file
View file

@ -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

View file

@ -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 <momo.h>
#define _(_str) momo_gettext(_str)
#else
#include <libintl.h>
#define _(_str) gettext(_str)
#endif
#else
#define _(_str) _str
#endif