Add localization "foundation" (many code changes)

This commit adds most of the code changes necessary for making the game
translatable, but does not yet "unhardcode" nearly all of the strings
(except in a few cases where it was hard to separate added
loc::gettexts from foundational code changes, or all the localization-
related menus which were also added by this commit.)

This commit is part of rewritten history of the localization branch.
The original (unsquashed) commit history can be found here:
https://github.com/Dav999-v/VVVVVV/tree/localization-orig
This commit is contained in:
Dav999-v
2022-12-30 22:57:24 +01:00
committed by Misa Elizabeth Kai
parent 35d92e8e64
commit ec611ffa9d
22 changed files with 1567 additions and 95 deletions

View File

@@ -6,12 +6,15 @@
#include "Alloc.h"
#include "BinaryBlob.h"
#include "Constants.h"
#include "Exit.h"
#include "Graphics.h"
#include "Localization.h"
#include "Maths.h"
#include "Screen.h"
#include "Unused.h"
#include "UtilityClass.h"
#include "VFormat.h"
#include "Vlogging.h"
/* These are needed for PLATFORM_* crap */
@@ -40,8 +43,11 @@ static bool isInit = false;
static const char* pathSep = NULL;
static char* basePath = NULL;
static char writeDir[MAX_PATH] = {'\0'};
static char saveDir[MAX_PATH] = {'\0'};
static char levelDir[MAX_PATH] = {'\0'};
static char mainLangDir[MAX_PATH] = {'\0'};
static bool isMainLangDirFromRepo = false;
static char assetDir[MAX_PATH] = {'\0'};
static char virtualMountPath[MAX_PATH] = {'\0'};
@@ -66,7 +72,105 @@ static const PHYSFS_Allocator allocator = {
SDL_free
};
int FILESYSTEM_init(char *argvZero, char* baseDir, char *assetsPath)
void mount_pre_datazip(
char* out_path,
const char* real_dirname,
const char* mount_point,
const char* user_path
)
{
/* Find and mount a directory (like the main language directory) in front of data.zip.
* This directory, if not user-supplied, can be either next to data.zip,
* or otherwise in desktop_version/ if that's found in the base path.
*
* out_path is assumed to be either NULL, or MAX_PATH long. If it isn't, boom */
if (user_path != NULL)
{
if (PHYSFS_mount(user_path, mount_point, 1))
{
if (out_path != NULL)
{
SDL_strlcpy(out_path, user_path, MAX_PATH);
}
}
else
{
vlog_warn("User-supplied %s directory is invalid!", real_dirname);
}
return;
}
/* Try to detect the directory, it's next to data.zip in distributed builds */
bool dir_found = false;
char buffer[MAX_PATH];
SDL_snprintf(buffer, sizeof(buffer), "%s%s%s",
basePath,
real_dirname,
pathSep
);
if (PHYSFS_mount(buffer, mount_point, 1))
{
dir_found = true;
}
else
{
/* If you're a developer, you probably want to use the language files/fonts
* from the repo, otherwise it's a pain to keep everything in sync.
* And who knows how deep in build folders our binary is. */
size_t buf_reserve = SDL_strlen(real_dirname)+1;
SDL_strlcpy(buffer, basePath, sizeof(buffer)-buf_reserve);
char needle[32];
SDL_snprintf(needle, sizeof(needle), "%sdesktop_version%s",
pathSep,
pathSep
);
/* We want the last match */
char* match_last = NULL;
char* match = buffer;
while ((match = SDL_strstr(match, needle)))
{
match_last = match;
match = &match[1];
}
if (match_last != NULL)
{
/* strstr only gives us a pointer and not a remaining buffer length, but that's
* why we pretended the buffer was `buf_reserve` chars shorter than it was! */
SDL_strlcpy(&match_last[SDL_strlen(needle)], real_dirname, buf_reserve);
SDL_strlcat(buffer, pathSep, sizeof(buffer));
if (PHYSFS_mount(buffer, mount_point, 1))
{
dir_found = true;
if (SDL_strcmp(real_dirname, "lang") == 0)
{
loc::show_translator_menu = true;
isMainLangDirFromRepo = true;
}
}
}
}
if (dir_found)
{
if (out_path != NULL)
{
SDL_strlcpy(out_path, buffer, MAX_PATH);
}
}
else
{
vlog_warn("Cannot find the %s directory anywhere!", real_dirname);
}
}
int FILESYSTEM_init(char *argvZero, char* baseDir, char *assetsPath, char* langDir, char* fontsDir)
{
char output[MAX_PATH];
@@ -102,29 +206,30 @@ int FILESYSTEM_init(char *argvZero, char* baseDir, char *assetsPath)
}
/* Mount our base user directory */
if (!PHYSFS_mount(output, NULL, 0))
SDL_strlcpy(writeDir, output, sizeof(writeDir));
if (!PHYSFS_mount(writeDir, NULL, 0))
{
vlog_error(
"Could not mount %s: %s",
output,
writeDir,
PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())
);
return 0;
}
if (!PHYSFS_setWriteDir(output))
if (!PHYSFS_setWriteDir(writeDir))
{
vlog_error(
"Could not set write dir to %s: %s",
output,
writeDir,
PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())
);
return 0;
}
vlog_info("Base directory: %s", output);
vlog_info("Base directory: %s", writeDir);
/* Store full save directory */
SDL_snprintf(saveDir, sizeof(saveDir), "%s%s%s",
output,
writeDir,
"saves",
pathSep
);
@@ -133,7 +238,7 @@ int FILESYSTEM_init(char *argvZero, char* baseDir, char *assetsPath)
/* Store full level directory */
SDL_snprintf(levelDir, sizeof(levelDir), "%s%s%s",
output,
writeDir,
"levels",
pathSep
);
@@ -148,6 +253,11 @@ int FILESYSTEM_init(char *argvZero, char* baseDir, char *assetsPath)
basePath = SDL_strdup("./");
}
mount_pre_datazip(mainLangDir, "lang", "lang/", langDir);
vlog_info("Languages directory: %s", mainLangDir);
mount_pre_datazip(NULL, "fonts", "graphics/", fontsDir);
/* Mount the stock content last */
if (assetsPath)
{
@@ -217,7 +327,40 @@ char *FILESYSTEM_getUserLevelDirectory(void)
return levelDir;
}
bool FILESYSTEM_isFile(const char* filename)
char *FILESYSTEM_getUserMainLangDirectory(void)
{
return mainLangDir;
}
bool FILESYSTEM_isMainLangDirFromRepo(void)
{
return isMainLangDirFromRepo;
}
bool FILESYSTEM_restoreWriteDir(void)
{
return PHYSFS_setWriteDir(writeDir);
}
bool FILESYSTEM_setLangWriteDir(void)
{
const char* realLangDir = PHYSFS_getRealDir("lang");
if (realLangDir == NULL || SDL_strcmp(mainLangDir, realLangDir) != 0)
{
vlog_error("Not setting language write dir: %s overrules %s when loading",
realLangDir, mainLangDir
);
return false;
}
if (!PHYSFS_setWriteDir(mainLangDir))
{
FILESYSTEM_restoreWriteDir();
return false;
}
return true;
}
bool FILESYSTEM_isFileType(const char* filename, PHYSFS_FileType filetype)
{
PHYSFS_Stat stat;
@@ -235,10 +378,20 @@ bool FILESYSTEM_isFile(const char* filename)
/* We unfortunately cannot follow symlinks (PhysFS limitation).
* Let the caller deal with them.
*/
return stat.filetype == PHYSFS_FILETYPE_REGULAR
return stat.filetype == filetype
|| stat.filetype == PHYSFS_FILETYPE_SYMLINK;
}
bool FILESYSTEM_isFile(const char* filename)
{
return FILESYSTEM_isFileType(filename, PHYSFS_FILETYPE_REGULAR);
}
bool FILESYSTEM_isDirectory(const char* filename)
{
return FILESYSTEM_isFileType(filename, PHYSFS_FILETYPE_DIRECTORY);
}
bool FILESYSTEM_isMounted(const char* filename)
{
return PHYSFS_getMountPoint(filename) != NULL;
@@ -282,7 +435,7 @@ static void generateVirtualMountPath(char* path, const size_t path_size)
);
}
static char levelDirError[256] = {'\0'};
static char levelDirError[6*SCREEN_WIDTH_CHARS + 1] = {'\0'};
static bool levelDirHasError = false;
@@ -501,6 +654,31 @@ bool FILESYSTEM_isAssetMounted(const char* filename)
return SDL_strcmp(assetDir, realDir) == 0;
}
bool FILESYSTEM_areAssetsInSameRealDir(const char* filenameA, const char* filenameB)
{
char pathA[MAX_PATH];
char pathB[MAX_PATH];
getMountedPath(pathA, sizeof(pathA), filenameA);
getMountedPath(pathB, sizeof(pathB), filenameB);
const char* realDirA = PHYSFS_getRealDir(pathA);
const char* realDirB = PHYSFS_getRealDir(pathB);
/* Both NULL, or both the same pointer? */
if (realDirA == realDirB)
{
return true;
}
if (realDirA == NULL || realDirB == NULL)
{
return false;
}
return SDL_strcmp(realDirA, realDirB) == 0;
}
static void load_stdin(void)
{
size_t pos = 0;
@@ -809,6 +987,20 @@ bool FILESYSTEM_loadTiXml2Document(const char *name, tinyxml2::XMLDocument& doc)
return true;
}
bool FILESYSTEM_loadAssetTiXml2Document(const char *name, tinyxml2::XMLDocument& doc)
{
/* Same as FILESYSTEM_loadTiXml2Document except for possible custom assets */
unsigned char *mem;
FILESYSTEM_loadAssetToMemory(name, &mem, NULL, true);
if (mem == NULL)
{
return false;
}
doc.Parse((const char*) mem);
VVV_free(mem);
return true;
}
struct CallbackWrapper
{
void (*callback)(const char* filename);
@@ -853,6 +1045,28 @@ void FILESYSTEM_enumerateLevelDirFileNames(
}
}
std::vector<std::string> FILESYSTEM_getLanguageCodes(void)
{
std::vector<std::string> list;
char** fileList = PHYSFS_enumerateFiles("lang");
char** item;
for (item = fileList; *item != NULL; item++)
{
char fullName[128];
SDL_snprintf(fullName, sizeof(fullName), "lang/%s", *item);
if (FILESYSTEM_isDirectory(fullName) && *item[0] != '.')
{
list.push_back(*item);
}
}
PHYSFS_freeList(fileList);
return list;
}
static int PLATFORM_getOSDirectory(char* output, const size_t output_size)
{
#ifdef _WIN32