2004-08-23 00:15:46 +00:00
# include "quakedef.h"
2020-08-13 08:39:48 +00:00
//#define COLOURMISSINGSTRINGS //for english people to more easily see what's not translatable (text still white)
//#define COLOURUNTRANSLATEDSTRINGS //show empty translations as alt-text versions of the original string
2004-08-23 00:15:46 +00:00
//client may remap messages from the server to a regional bit of text.
//server may remap progs messages
//basic language is english (cos that's what (my version of) Quake uses).
//translate is english->lang
//untranslate is lang->english for console commands.
2024-07-14 18:56:31 +00:00
static void FilterPurge ( void ) ;
static void FilterInit ( const char * file ) ;
2004-08-23 00:15:46 +00:00
2018-12-28 00:04:36 +00:00
int com_language ;
2013-11-21 23:02:28 +00:00
char sys_language [ 64 ] = " " ;
2015-05-03 19:57:46 +00:00
static char langpath [ MAX_OSPATH ] = " " ;
2014-02-07 08:38:40 +00:00
struct language_s languages [ MAX_LANGUAGES ] ;
2004-08-23 00:15:46 +00:00
2015-04-21 04:12:00 +00:00
static void QDECL TL_LanguageChanged ( struct cvar_s * var , char * oldvalue )
2004-08-23 00:15:46 +00:00
{
2018-12-28 00:04:36 +00:00
com_language = TL_FindLanguage ( var - > string ) ;
2004-08-23 00:15:46 +00:00
}
Added sys_openfile console command(and menu option) to web and flatpak(via cmake+dbus) builds, to 'install' packages on sandboxed systems a bit more easily.
Cmake: Add FTE_WERROR option, defaults to true in debug builds and off in release builds (in case future compilers have issues).
Cmake: Pull in libXscreensaver so we don't get interrupted by screensavers when playing demos.
Make: Added `make webcl-rel` for a web build without server bloat (eg for sites focused on demo playback. Yes, this means you XantoM).
fteqcc: Include the decompiler in fteqcc (non-gui) builds ('-d' arg).
fteqcc: Decompiler can now mostly handle hexen2 mods without any unknown opcodes.
Allow ezHud and OpenSSL to be compiled as in-engine plugins, potentially for web and windows ports respectively.
Web: Fix support for ogg vorbis. Add support for voip.
Web: Added basic support for WebXR.
QTV: Don't try seeking on unseekable qtv streams. Don't spam when developer 1 is set.
QTV: add support for some eztv extensions.
MVD: added hack to use ktx's vweps in mvd where mvdsv doesn't bother to record the info.
qwfwd: hack around a hack in qwfwd, allowing it to work again.
recording: favour qwd in single player, instead of mvd.
Protocol: reduce client memory used for precache names. Bump maximum precache counts - some people are just abusive, yes you Orl.
hexen2: add enough clientside protocol compat to play the demo included with h2mp. lacks effects.
in_xflip: restored this setting.
fs_hidesyspaths: new cvar, defaults to enabled so you won't find your username or whatever turning up in screenshots or the like. change it to 0 before debuging stuff eg via 'path'.
gl_overbright_models: Added cvar to match QS.
netchan: Added MTU determination, we'll no longer fail to connect when routers stupidly drop icmp packets.
Win: try a few other versions of xinput too.
CSQC: Added a CSQC_GenerateMaterial function, to give the csqc a chance to generate custom materials.
MenuQC: Added support for the skeletal objects API.
2024-04-09 17:13:59 +00:00
cvar_t language = CVARAFCD ( " lang " , sys_language , " prvm_language " , CVAR_USERINFO | CVAR_NORESET /*otherwise gamedir switches will be annoying*/ , TL_LanguageChanged , " This cvar contains the language_dialect code of your language, used to find localisation strings. " ) ;
2004-08-23 00:15:46 +00:00
2024-07-14 18:56:31 +00:00
static void Filter_Reload_f ( void )
{
// FilterInit(
}
2004-08-23 00:15:46 +00:00
void TranslateInit ( void )
{
2024-07-14 18:56:31 +00:00
Cmd_AddCommand ( " com_reloadfilter " , Filter_Reload_f ) ;
2018-12-28 00:04:36 +00:00
Cvar_Register ( & language , " Internationalisation " ) ;
2004-08-23 00:15:46 +00:00
}
2013-11-29 14:36:47 +00:00
void TL_Shutdown ( void )
2004-08-23 00:15:46 +00:00
{
2013-11-29 14:36:47 +00:00
int j ;
2004-08-23 00:15:46 +00:00
2013-11-29 14:36:47 +00:00
for ( j = 0 ; j < MAX_LANGUAGES ; j + + )
2004-08-23 00:15:46 +00:00
{
2013-11-29 14:36:47 +00:00
if ( ! languages [ j ] . name )
2004-08-23 00:15:46 +00:00
continue ;
2013-11-29 14:36:47 +00:00
free ( languages [ j ] . name ) ;
languages [ j ] . name = NULL ;
PO_Close ( languages [ j ] . po ) ;
languages [ j ] . po = NULL ;
2023-01-09 05:11:34 +00:00
PO_Close ( languages [ j ] . po_qex ) ;
languages [ j ] . po_qex = NULL ;
2004-08-23 00:15:46 +00:00
}
2024-07-14 18:56:31 +00:00
FilterPurge ( ) ;
2004-08-23 00:15:46 +00:00
}
2013-11-29 14:36:47 +00:00
static int TL_LoadLanguage ( char * lang )
2004-08-23 00:15:46 +00:00
{
2013-11-29 14:36:47 +00:00
vfsfile_t * f ;
int j ;
char * u ;
for ( j = 0 ; j < MAX_LANGUAGES ; j + + )
2004-08-23 00:15:46 +00:00
{
2013-11-29 14:36:47 +00:00
if ( ! languages [ j ] . name )
break ;
if ( ! stricmp ( languages [ j ] . name , lang ) )
return j ;
2004-08-23 00:15:46 +00:00
}
2011-05-15 13:23:13 +00:00
2013-11-29 14:36:47 +00:00
//err... oops, ran out of languages...
if ( j = = MAX_LANGUAGES )
return 0 ;
2004-08-23 00:15:46 +00:00
2013-11-29 14:36:47 +00:00
if ( * lang )
2015-05-03 19:57:46 +00:00
f = FS_OpenVFS ( va ( " %sfteqw.%s.po " , langpath , lang ) , " rb " , FS_SYSTEM ) ;
2013-11-29 14:36:47 +00:00
else
f = NULL ;
if ( ! f & & * lang )
2004-08-23 00:15:46 +00:00
{
2013-11-29 14:36:47 +00:00
//keep truncating until we can find a name that works
u = strrchr ( lang , ' _ ' ) ;
if ( u )
2023-01-09 05:11:34 +00:00
{
2013-11-29 14:36:47 +00:00
* u = 0 ;
2023-01-09 05:11:34 +00:00
return TL_LoadLanguage ( lang ) ;
}
2004-08-23 00:15:46 +00:00
}
2013-11-29 14:36:47 +00:00
languages [ j ] . name = strdup ( lang ) ;
2017-10-12 12:02:25 +00:00
languages [ j ] . po = NULL ;
2023-05-27 17:00:32 +00:00
# if !defined(COLOURUNTRANSLATEDSTRINGS) && !defined(COLOURMISSINGSTRINGS)
2017-10-12 12:02:25 +00:00
if ( f )
2020-08-13 08:39:48 +00:00
# endif
2017-10-12 12:02:25 +00:00
{
languages [ j ] . po = PO_Create ( ) ;
PO_Merge ( languages [ j ] . po , f ) ;
}
2011-05-15 13:23:13 +00:00
2013-11-29 14:36:47 +00:00
return j ;
2004-08-23 00:15:46 +00:00
}
2013-11-29 14:36:47 +00:00
int TL_FindLanguage ( const char * lang )
2012-07-05 19:42:36 +00:00
{
2013-11-29 14:36:47 +00:00
char trimname [ 64 ] ;
Q_strncpyz ( trimname , lang , sizeof ( trimname ) ) ;
return TL_LoadLanguage ( trimname ) ;
2012-07-05 19:42:36 +00:00
}
2013-11-29 14:36:47 +00:00
//need to set up default languages for any early prints before cvars are inited.
2016-11-20 20:52:41 +00:00
void TL_InitLanguages ( const char * newlangpath )
2004-08-23 00:15:46 +00:00
{
2013-11-29 14:36:47 +00:00
int i ;
char * lang ;
2004-08-23 00:15:46 +00:00
2016-02-10 23:23:43 +00:00
if ( ! newlangpath )
newlangpath = " " ;
2015-05-03 19:57:46 +00:00
Q_strncpyz ( langpath , newlangpath , sizeof ( langpath ) ) ;
2013-11-29 14:36:47 +00:00
//lang can override any environment or system settings.
2004-08-23 00:15:46 +00:00
if ( ( i = COM_CheckParm ( " -lang " ) ) )
2013-11-29 14:36:47 +00:00
Q_strncpyz ( sys_language , com_argv [ i + 1 ] , sizeof ( sys_language ) ) ;
2004-08-23 00:15:46 +00:00
else
2013-11-29 14:36:47 +00:00
{
lang = NULL ;
if ( ! lang )
lang = getenv ( " LANGUAGE " ) ;
if ( ! lang )
lang = getenv ( " LC_ALL " ) ;
if ( ! lang )
lang = getenv ( " LC_MESSAGES " ) ;
if ( ! lang )
lang = getenv ( " LANG " ) ;
if ( ! lang )
lang = " " ;
if ( ! strcmp ( lang , " C " ) | | ! strcmp ( lang , " POSIX " ) )
lang = " " ;
//windows will have already set the locale from the windows settings, so only replace it if its actually valid.
if ( * lang )
Q_strncpyz ( sys_language , lang , sizeof ( sys_language ) ) ;
}
2004-08-23 00:15:46 +00:00
2013-11-29 14:36:47 +00:00
//clean it up.
//takes the form: [language[_territory][.codeset][@modifier]]
//we don't understand modifiers
lang = strrchr ( sys_language , ' @ ' ) ;
if ( lang )
* lang = 0 ;
//we don't understand codesets sadly.
lang = strrchr ( sys_language , ' . ' ) ;
2014-08-03 14:47:47 +00:00
if ( lang )
* lang = 0 ;
//we also only support the single primary locale (no fallbacks, we're just using the language[+territory])
lang = strrchr ( sys_language , ' : ' ) ;
2013-11-29 14:36:47 +00:00
if ( lang )
* lang = 0 ;
//but we do support territories.
2018-12-28 00:04:36 +00:00
com_language = TL_FindLanguage ( sys_language ) ;
2004-08-23 00:15:46 +00:00
2013-11-29 14:36:47 +00:00
//make sure a fallback exists, but not as language 0
TL_FindLanguage ( " " ) ;
2004-08-23 00:15:46 +00:00
}
2014-09-17 03:04:08 +00:00
# ifdef HEXEN2
2004-08-23 00:15:46 +00:00
//this stuff is for hexen2 translation strings.
//(hexen2 is uuuuggllyyyy...)
2010-08-16 02:03:02 +00:00
static char * strings_list ;
static char * * strings_table ;
static int strings_count ;
static qboolean strings_loaded ;
2004-08-23 00:15:46 +00:00
void T_FreeStrings ( void )
{ //on map change, following gamedir change
if ( strings_loaded )
{
BZ_Free ( strings_list ) ;
BZ_Free ( strings_table ) ;
strings_count = 0 ;
strings_loaded = false ;
}
}
void T_LoadString ( void )
{
int i ;
char * s , * s2 ;
//count new lines
strings_loaded = true ;
strings_count = 0 ;
2014-10-05 20:04:11 +00:00
strings_list = FS_LoadMallocFile ( " strings.txt " , NULL ) ;
2004-08-23 00:15:46 +00:00
if ( ! strings_list )
return ;
for ( s = strings_list ; * s ; s + + )
{
if ( * s = = ' \n ' )
strings_count + + ;
}
strings_table = BZ_Malloc ( sizeof ( char * ) * strings_count ) ;
s = strings_list ;
for ( i = 0 ; i < strings_count ; i + + )
{
strings_table [ i ] = s ;
s2 = strchr ( s , ' \n ' ) ;
if ( ! s2 )
break ;
while ( s < s2 )
{
if ( * s = = ' \r ' )
* s = ' \0 ' ;
else if ( * s = = ' ^ ' | | * s = = ' @ ' ) //becomes new line
* s = ' \n ' ;
s + + ;
}
s = s2 + 1 ;
* s2 = ' \0 ' ;
}
}
char * T_GetString ( int num )
{
if ( ! strings_loaded )
{
T_LoadString ( ) ;
}
if ( num < 0 | | num > = strings_count )
return " BAD STRING " ;
return strings_table [ num ] ;
}
2010-08-16 02:03:02 +00:00
2018-12-28 00:04:36 +00:00
# ifdef HAVE_CLIENT
2013-11-21 23:02:28 +00:00
//for hexen2's objectives and stuff.
2010-08-16 02:03:02 +00:00
static char * info_strings_list ;
static char * * info_strings_table ;
static int info_strings_count ;
static qboolean info_strings_loaded ;
void T_FreeInfoStrings ( void )
{ //on map change, following gamedir change
if ( info_strings_loaded )
{
BZ_Free ( info_strings_list ) ;
BZ_Free ( info_strings_table ) ;
info_strings_count = 0 ;
info_strings_loaded = false ;
}
}
void T_LoadInfoString ( void )
{
int i ;
char * s , * s2 ;
//count new lines
info_strings_loaded = true ;
info_strings_count = 0 ;
2014-10-05 20:04:11 +00:00
info_strings_list = FS_LoadMallocFile ( " infolist.txt " , NULL ) ;
2010-08-16 02:03:02 +00:00
if ( ! info_strings_list )
return ;
for ( s = info_strings_list ; * s ; s + + )
{
if ( * s = = ' \n ' )
info_strings_count + + ;
}
info_strings_table = BZ_Malloc ( sizeof ( char * ) * info_strings_count ) ;
s = info_strings_list ;
for ( i = 0 ; i < info_strings_count ; i + + )
{
info_strings_table [ i ] = s ;
s2 = strchr ( s , ' \n ' ) ;
if ( ! s2 )
break ;
while ( s < s2 )
{
if ( * s = = ' \r ' )
* s = ' \0 ' ;
else if ( * s = = ' ^ ' | | * s = = ' @ ' ) //becomes new line
* s = ' \n ' ;
s + + ;
}
s = s2 + 1 ;
* s2 = ' \0 ' ;
}
}
char * T_GetInfoString ( int num )
{
if ( ! info_strings_loaded )
{
T_LoadInfoString ( ) ;
}
if ( num < 0 | | num > = info_strings_count )
return " BAD STRING " ;
2004-08-23 00:15:46 +00:00
2010-08-16 02:03:02 +00:00
return info_strings_table [ num ] ;
}
2012-01-17 07:57:46 +00:00
# endif
2014-09-17 03:04:08 +00:00
# endif
2012-01-17 07:57:46 +00:00
2013-11-21 23:02:28 +00:00
struct poline_s
{
bucket_t buck ;
struct poline_s * next ;
char * orig ;
char * translated ;
} ;
struct po_s
{
hashtable_t hash ;
struct poline_s * lines ;
} ;
2020-08-13 08:39:48 +00:00
static struct poline_s * PO_AddText ( struct po_s * po , const char * orig , const char * trans )
2023-05-27 17:00:32 +00:00
{ //input is assumed to be utf-8, but that's not always what quake uses. on the plus side we do have our own silly markup to handle unicode (and colours etc).
2013-11-21 23:02:28 +00:00
size_t olen = strlen ( orig ) + 1 ;
2023-05-27 17:00:32 +00:00
size_t tlen ;
struct poline_s * line ;
const char * s ;
char temp [ 64 ] ;
//figure out the required length for the encoding we're actually going to use
if ( com_parseutf8 . ival ! = 1 )
{
tlen = 0 ;
for ( s = trans , tlen = 0 ; * s ; )
{
unsigned int err ;
unsigned int chr = utf8_decode ( & err , s , & s ) ;
tlen + = unicode_encode ( temp , chr , sizeof ( temp ) , true ) ;
}
tlen + + ;
}
else
tlen = strlen ( trans ) + 1 ;
line = Z_Malloc ( sizeof ( * line ) + olen + tlen ) ;
2013-11-21 23:02:28 +00:00
memcpy ( line + 1 , orig , olen ) ;
orig = ( const char * ) ( line + 1 ) ;
line - > translated = ( char * ) ( line + 1 ) + olen ;
2023-05-27 17:00:32 +00:00
if ( com_parseutf8 . ival ! = 1 )
{
//do the loop again now we know we've got enough space for it.
for ( s = trans , tlen = 0 ; * s ; )
{
unsigned int err ;
unsigned int chr = utf8_decode ( & err , s , & s ) ;
tlen + = unicode_encode ( line - > translated + tlen , chr , sizeof ( temp ) , true ) ;
}
line - > translated [ tlen ] = 0 ;
}
else
memcpy ( line - > translated , trans , tlen ) ;
2013-11-21 23:02:28 +00:00
trans = ( const char * ) ( line - > translated ) ;
Hash_Add ( & po - > hash , orig , line , & line - > buck ) ;
2013-11-29 14:36:47 +00:00
line - > next = po - > lines ;
po - > lines = line ;
2020-08-13 08:39:48 +00:00
return line ;
2013-11-21 23:02:28 +00:00
}
2017-10-12 12:02:25 +00:00
void PO_Merge ( struct po_s * po , vfsfile_t * file )
2013-11-21 23:02:28 +00:00
{
2013-11-29 14:36:47 +00:00
char * instart , * in , * end ;
2013-11-21 23:02:28 +00:00
int inlen ;
char msgid [ 32768 ] ;
char msgstr [ 32768 ] ;
2020-08-13 08:39:48 +00:00
struct {
quint32_t magic ;
quint32_t revision ;
quint32_t numstrings ;
quint32_t offset_orig ;
quint32_t offset_trans ;
// quint32_t hashsize;
// quint32_t offset_hash;
} * moheader ;
2013-11-21 23:02:28 +00:00
2013-11-29 14:36:47 +00:00
qboolean allowblanks = ! ! COM_CheckParm ( " -translatetoblank " ) ;
2017-10-12 12:02:25 +00:00
if ( ! file )
return ;
2013-11-21 23:02:28 +00:00
inlen = file ? VFS_GETLEN ( file ) : 0 ;
2013-11-29 14:36:47 +00:00
instart = in = BZ_Malloc ( inlen + 1 ) ;
2013-11-21 23:02:28 +00:00
if ( file )
VFS_READ ( file , in , inlen ) ;
in [ inlen ] = 0 ;
if ( file )
VFS_CLOSE ( file ) ;
2020-08-13 08:39:48 +00:00
moheader = ( void * ) in ;
if ( inlen > = sizeof ( * moheader ) & & moheader - > magic = = 0x950412de )
2013-11-21 23:02:28 +00:00
{
2020-08-13 08:39:48 +00:00
struct
2013-11-21 23:02:28 +00:00
{
2020-08-13 08:39:48 +00:00
quint32_t length ;
quint32_t offset ;
} * src = ( void * ) ( in + moheader - > offset_orig ) , * dst = ( void * ) ( in + moheader - > offset_trans ) ;
quint32_t i ;
for ( i = moheader - > numstrings ; i - - > 0 ; src + + , dst + + )
PO_AddText ( po , in + src - > offset , in + dst - > offset ) ;
}
else
{
end = in + inlen ;
while ( in < end )
2013-11-21 23:02:28 +00:00
{
2020-08-13 08:39:48 +00:00
while ( * in = = ' ' | | * in = = ' \n ' | | * in = = ' \r ' | | * in = = ' \t ' )
in + + ;
if ( * in = = ' # ' )
2013-11-21 23:02:28 +00:00
{
2020-08-13 08:39:48 +00:00
while ( * in & & * in ! = ' \n ' )
2013-11-21 23:02:28 +00:00
in + + ;
2020-08-13 08:39:48 +00:00
}
else if ( ! strncmp ( in , " msgid " , 5 ) & & ( in [ 5 ] = = ' ' | | in [ 5 ] = = ' \t ' | | in [ 5 ] = = ' \r ' | | in [ 5 ] = = ' \n ' ) )
{
size_t start = 0 ;
size_t ofs = 0 ;
in + = 5 ;
while ( 1 )
2013-11-21 23:02:28 +00:00
{
2020-08-13 08:39:48 +00:00
while ( * in = = ' ' | | * in = = ' \n ' | | * in = = ' \r ' | | * in = = ' \t ' )
in + + ;
if ( * in = = ' \" ' )
{
in = COM_ParseCString ( in , msgid + start , sizeof ( msgid ) - start , & ofs ) ;
start + = ofs ;
}
else
break ;
2013-11-21 23:02:28 +00:00
}
}
2020-08-13 08:39:48 +00:00
else if ( ! strncmp ( in , " msgstr " , 6 ) & & ( in [ 6 ] = = ' ' | | in [ 6 ] = = ' \t ' | | in [ 6 ] = = ' \r ' | | in [ 6 ] = = ' \n ' ) )
2013-11-21 23:02:28 +00:00
{
2020-08-13 08:39:48 +00:00
size_t start = 0 ;
size_t ofs = 0 ;
in + = 6 ;
while ( 1 )
2013-11-21 23:02:28 +00:00
{
2020-08-13 08:39:48 +00:00
while ( * in = = ' ' | | * in = = ' \n ' | | * in = = ' \r ' | | * in = = ' \t ' )
in + + ;
if ( * in = = ' \" ' )
{
in = COM_ParseCString ( in , msgstr + start , sizeof ( msgstr ) - start , & ofs ) ;
start + = ofs ;
}
else
break ;
2013-11-21 23:02:28 +00:00
}
2020-08-13 08:39:48 +00:00
if ( ( * msgid & & start ) | | allowblanks )
PO_AddText ( po , msgid , msgstr ) ;
# ifdef COLOURUNTRANSLATEDSTRINGS
else if ( ! start )
{
char temp [ 1024 ] ;
int i ;
Q_snprintfz ( temp , sizeof ( temp ) , " %s " , * msgstr ? msgstr : msgid ) ;
for ( i = 0 ; temp [ i ] ; i + + )
{
if ( temp [ i ] = = ' % ' )
{
while ( temp [ i ] > ' ' )
i + + ;
}
else if ( temp [ i ] > = ' ' )
temp [ i ] | = 0x80 ;
}
PO_AddText ( po , msgid , temp ) ;
}
# endif
}
else
{
//some sort of junk?
2013-11-21 23:02:28 +00:00
in + + ;
2020-08-13 08:39:48 +00:00
while ( * in & & * in ! = ' \n ' )
in + + ;
}
2013-11-21 23:02:28 +00:00
}
}
2013-11-29 14:36:47 +00:00
BZ_Free ( instart ) ;
2017-10-12 12:02:25 +00:00
}
struct po_s * PO_Create ( void )
{
struct po_s * po ;
unsigned int buckets = 1024 ;
po = Z_Malloc ( sizeof ( * po ) + Hash_BytesForBuckets ( buckets ) ) ;
Hash_InitTable ( & po - > hash , buckets , po + 1 ) ;
2013-11-21 23:02:28 +00:00
return po ;
}
void PO_Close ( struct po_s * po )
{
2013-11-29 14:36:47 +00:00
if ( ! po )
return ;
2013-11-21 23:02:28 +00:00
while ( po - > lines )
{
struct poline_s * r = po - > lines ;
po - > lines = r - > next ;
Z_Free ( r ) ;
}
Z_Free ( po ) ;
}
2020-08-13 08:39:48 +00:00
const char * PO_GetText ( struct po_s * po , const char * msg )
{
struct poline_s * line ;
2023-05-27 17:00:32 +00:00
if ( ! po | | ! msg )
2020-08-13 08:39:48 +00:00
return msg ;
line = Hash_Get ( & po - > hash , msg ) ;
# ifdef COLOURMISSINGSTRINGS
if ( ! line )
{
char temp [ 1024 ] ;
int i ;
2023-05-27 17:00:32 +00:00
const char * in = msg ;
for ( i = 0 ; * in & & i < sizeof ( temp ) - 1 ; )
2020-08-13 08:39:48 +00:00
{
2023-05-27 17:00:32 +00:00
if ( * in = = ' % ' )
{ //don't mess up % formatting too much
while ( * in > ' ' & & i < sizeof ( temp ) - 1 )
temp [ i + + ] = * in + + ;
2020-08-13 08:39:48 +00:00
}
2023-05-27 17:00:32 +00:00
else if ( in > ' ' & & * in < 128 ) //otherwise force any ascii chars to the 0xe0XX range so it doesn't use any freetype fonts so its instantly recognisable as bad.
i + = utf8_encode ( temp + i , * in + + | 0xe080 , sizeof ( temp ) - 1 - i ) ;
else
temp [ i + + ] = * in + + ; //don't mess with any c0/extended codepoints
2020-08-13 08:39:48 +00:00
}
2023-05-27 17:00:32 +00:00
temp [ i ] = 0 ;
2020-08-13 08:39:48 +00:00
line = PO_AddText ( po , msg , temp ) ;
}
# endif
if ( line )
return line - > translated ;
return msg ;
}
2021-08-23 06:37:21 +00:00
2023-01-09 05:11:34 +00:00
static void PO_Merge_Rerelease ( struct po_s * po , const char * langname , const char * fmt )
2021-08-23 06:37:21 +00:00
{
//FOO <plat,plat> = "CString"
char line [ 32768 ] ;
char key [ 256 ] ;
char val [ 32768 ] ;
char * s ;
vfsfile_t * file = NULL ;
2023-01-09 05:11:34 +00:00
if ( ! file & & * langname ) //use system locale names
file = FS_OpenVFS ( va ( fmt , langname ) , " rb " , FS_GAME ) ;
2021-08-23 06:37:21 +00:00
if ( ! file ) //make a guess
{
s = NULL ;
2023-01-09 05:11:34 +00:00
if ( langname [ 0 ] & & langname [ 1 ] & & ( ! langname [ 2 ] | | langname [ 2 ] = = ' - ' | | langname [ 2 ] = = ' _ ' ) )
2021-08-23 06:37:21 +00:00
{ //try to map the user's formal locale to the rerelease's arbitrary names (at least from the perspective of anyone who doesn't speak english).
2023-01-09 05:11:34 +00:00
if ( ! strncmp ( langname , " fr " , 2 ) )
2021-08-23 06:37:21 +00:00
s = " french " ;
2023-01-09 05:11:34 +00:00
else if ( ! strncmp ( langname , " de " , 2 ) )
2021-08-23 06:37:21 +00:00
s = " german " ;
2023-01-09 05:11:34 +00:00
else if ( ! strncmp ( langname , " it " , 2 ) )
2021-08-23 06:37:21 +00:00
s = " italian " ;
2023-01-09 05:11:34 +00:00
else if ( ! strncmp ( langname , " ru " , 2 ) )
2021-08-23 06:37:21 +00:00
s = " russian " ;
2023-01-09 05:11:34 +00:00
else if ( ! strncmp ( langname , " es " , 2 ) )
2021-08-23 06:37:21 +00:00
s = " spanish " ;
}
if ( s )
file = FS_OpenVFS ( va ( fmt , s ) , " rb " , FS_GAME ) ;
}
if ( ! file ) //fall back on misnamed american, for lack of a better default.
file = FS_OpenVFS ( va ( fmt , " english " ) , " rb " , FS_GAME ) ;
if ( file )
{
* key = ' $ ' ;
while ( VFS_GETS ( file , line , sizeof ( line ) ) )
{
s = COM_ParseOut ( line , key + 1 , sizeof ( key ) - 1 ) ;
s = COM_ParseOut ( s , val , sizeof ( val ) ) ;
if ( strcmp ( val , " = " ) )
continue ;
s = COM_ParseCString ( s , val , sizeof ( val ) , NULL ) ;
if ( ! s )
continue ;
PO_AddText ( po , key , val ) ;
}
VFS_CLOSE ( file ) ;
}
}
2023-01-09 05:11:34 +00:00
const char * TL_Translate ( int language , const char * src )
2021-08-23 06:37:21 +00:00
{
2021-08-24 06:06:05 +00:00
if ( * src = = ' $ ' )
2021-08-23 06:37:21 +00:00
{
2023-01-09 05:11:34 +00:00
if ( ! languages [ language ] . po_qex )
2021-08-23 06:37:21 +00:00
{
2021-08-24 06:06:05 +00:00
char lang [ 64 ] , * h ;
vfsfile_t * f = NULL ;
2023-01-09 05:11:34 +00:00
languages [ language ] . po_qex = PO_Create ( ) ;
PO_Merge_Rerelease ( languages [ language ] . po_qex , languages [ language ] . name , " localization/loc_%s.txt " ) ;
2021-08-24 06:06:05 +00:00
2023-01-09 05:11:34 +00:00
Q_strncpyz ( lang , languages [ language ] . name , sizeof ( lang ) ) ;
2021-08-24 06:06:05 +00:00
while ( ( h = strchr ( lang , ' - ' ) ) )
* h = ' _ ' ; //standardise it
if ( * lang )
f = FS_OpenVFS ( va ( " localisation/%s.po " , lang ) , " rb " , FS_GAME ) ; //long/specific form
if ( ! f )
2021-08-23 06:37:21 +00:00
{
2021-08-24 06:06:05 +00:00
if ( ( h = strchr ( lang , ' _ ' ) ) )
2021-08-23 06:37:21 +00:00
{
2021-08-24 06:06:05 +00:00
* h = 0 ;
if ( * lang )
f = FS_OpenVFS ( va ( " localisation/%s.po " , lang ) , " rb " , FS_GAME ) ; //short/general form
2021-08-23 06:37:21 +00:00
}
}
2021-08-24 06:06:05 +00:00
if ( f )
2023-01-09 05:11:34 +00:00
PO_Merge ( languages [ language ] . po_qex , f ) ;
2021-08-23 06:37:21 +00:00
}
2023-01-09 05:11:34 +00:00
src = PO_GetText ( languages [ language ] . po_qex , src ) ;
2021-08-23 06:37:21 +00:00
}
2021-08-24 06:06:05 +00:00
return src ;
}
2023-01-09 05:11:34 +00:00
void TL_Reformat ( int language , char * out , size_t outsize , size_t numargs , const char * * arg )
2021-08-24 06:06:05 +00:00
{
const char * fmt ;
const char * a ;
size_t alen ;
2021-08-23 06:37:21 +00:00
fmt = ( numargs > 0 & & arg [ 0 ] ) ? arg [ 0 ] : " " ;
2023-01-09 05:11:34 +00:00
fmt = TL_Translate ( language , fmt ) ;
2021-08-23 06:37:21 +00:00
outsize - - ;
while ( outsize > 0 )
{
if ( ! * fmt )
break ;
2021-10-05 05:05:43 +00:00
else if ( * fmt = = ' { ' & & fmt [ 1 ] = = ' { ' )
* out + + = ' { ' , fmt + = 2 , outsize - - ;
else if ( * fmt = = ' } ' & & fmt [ 1 ] = = ' } ' )
* out + + = ' } ' , fmt + = 2 , outsize - - ;
2021-08-23 06:37:21 +00:00
else if ( * fmt = = ' { ' )
{
unsigned int index = strtoul ( fmt + 1 , ( char * * ) & fmt , 10 ) + 1 ;
int size = 0 ;
if ( * fmt = = ' , ' )
size = strtol ( fmt + 1 , ( char * * ) & fmt , 10 ) ;
if ( * fmt = = ' : ' )
{ //formatting, which we don't support because its all strings.
fmt = fmt + 1 ;
while ( * fmt & & * fmt ! = ' } ' )
fmt + + ;
}
if ( * fmt = = ' } ' )
fmt + + ;
else
break ; //some formatting error
if ( index > = numargs | | ! arg [ index ] )
a = " " ;
else
2023-01-09 05:11:34 +00:00
a = TL_Translate ( language , arg [ index ] ) ;
2021-08-23 06:37:21 +00:00
alen = strlen ( a ) ;
if ( alen > outsize )
alen = outsize ;
if ( size > 0 )
{ //right aligned
if ( alen > size )
alen = size ;
memcpy ( out , a , alen ) ;
}
else if ( size < 0 )
{ //left aligned
if ( alen > - size )
alen = - size ;
memcpy ( out , a , alen ) ;
}
else //no alignment, no padding.
memcpy ( out , a , alen ) ;
out + = alen ;
outsize - = alen ;
}
else
* out + + = * fmt + + , outsize - - ;
}
* out = 0 ;
}
2024-07-14 18:56:31 +00:00
# include <ctype.h>
static qbyte * filter [ 256 ] ; //one list per lead char, simple optimisation instead of some big decision tree.
static qbyte * filtermem ;
static int FilterCompareWords ( const void * v1 , const void * v2 )
{
const char * s1 = * ( const char * const * ) v1 ;
const char * s2 = * ( const char * const * ) v2 ;
return strcmp ( s2 , s1 ) ;
}
static void FilterPurge ( void )
{
memset ( filter , 0 , sizeof ( filter ) ) ;
free ( filtermem ) ;
filtermem = NULL ;
}
static void FilterInit ( const char * file )
{
qbyte * tempmem = malloc ( strlen ( file ) + 1 ) ;
qbyte * tempmemstart = tempmem ;
const char * * words ;
size_t count = 1 , i , l ;
size_t bytes ;
const char * c ;
FilterPurge ( ) ;
for ( c = file ; * c ; c + + )
if ( * c = = ' \n ' )
count + + ;
words = malloc ( sizeof ( qbyte * ) * count ) ;
count = 0 ;
for ( c = file ; * c ; )
{
while ( * c = = ' \n ' )
c + + ; //don't add 0-byte strings...
words [ count ] = tempmem ;
for ( ; * c ; c + + )
{
if ( * c = = ' ' )
continue ; //block even if they omit the spaces.
if ( * c = = ' \n ' )
break ;
* tempmem + + = tolower ( * c ) ;
}
* tempmem + + = 0 ;
count + + ;
}
qsort ( words , count , sizeof ( words [ 0 ] ) , FilterCompareWords ) ; //sort by lead byte... and longest first...
i = 0 ;
for ( i = 0 , bytes = 0 ; i < count ; i + + )
bytes + = strlen ( words [ i ] ) ;
bytes + = countof ( filter ) ;
filtermem = malloc ( bytes ) ;
for ( l = countof ( filter ) , i = 0 ; l - - > 0 ; )
{
if ( i < count & & words [ i ] [ 0 ] = = l )
{
filter [ l ] = filtermem ;
while ( i < count & & * words [ i ] = = l )
{ //second copy... urgh. can forget the first char and replace with a length.
* filtermem + + = strlen ( words [ i ] + 1 ) ;
memcpy ( filtermem , words [ i ] + 1 , filtermem [ - 1 ] ) ; //just the text, no null needed. tighly packed.
filtermem + = filtermem [ - 1 ] ;
i + + ;
}
* filtermem + + = 0 ;
}
else
filter [ l ] = NULL ;
}
free ( tempmemstart ) ;
free ( words ) ;
}
# define whiteish(c) (c == ',' || c == '.' || c == ' ' || c == '\t' || c == '\r' || c == '\n')
char * FilterObsceneString ( const qbyte * in , char * outbuf , size_t bufsize )
{ //input must be utf-8... if there's any ^ crap in there then strip it first. no bypassing filters with colour codes.
char * ret = outbuf ;
if ( strlen ( in ) > = bufsize )
Sys_Error ( " output buffer too small! " ) ;
restart :
while ( * in )
{
qbyte c = tolower ( * in ) ;
if ( filter [ c ] )
{
qbyte * m = filter [ c ] ;
while ( * m )
{ //for each word starting with this letter...
const qbyte * test = in + 1 ;
qbyte len = * m ;
const qbyte * match = m + 1 ;
m + = 1 + len ;
while ( * test )
{ //don't let 'foo bar' through when 'foobar' is a bad word.
if ( whiteish ( * test ) )
{
test + + ;
continue ;
}
if ( tolower ( * test ) = = * match )
{
test + + , match + + ;
if ( - - len = = 0 )
{ //a match.
if ( * test & & ! whiteish ( * test ) )
break ; //assassinate!
while ( test > in )
{ //censor it.
* outbuf = " #*@$ " [ ( outbuf - ret ) & 3 ] ;
outbuf + + ;
in + + ;
}
goto restart ; //double breaks suck
}
continue ;
}
break ;
}
}
}
while ( * in )
{
if ( whiteish ( * in ) )
{
* outbuf + + = * in + + ;
break ;
}
* outbuf + + = * in + + ;
}
}
* outbuf + + = 0 ; //make sure its null terminated.
return ret ;
}