Skip to content

Commit

Permalink
add initial language support using a simple MO parser
Browse files Browse the repository at this point in the history
  • Loading branch information
john-tornblom committed Apr 22, 2021
1 parent 86afa95 commit 8bf129f
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 1 deletion.
1 change: 1 addition & 0 deletions CMakeLists.txt
Expand Up @@ -241,6 +241,7 @@ set(devilutionx_SRCS
Source/inv.cpp
Source/itemdat.cpp
Source/items.cpp
Source/language.cpp
Source/lighting.cpp
Source/loadsave.cpp
Source/main.cpp
Expand Down
1 change: 1 addition & 0 deletions Source/DiabloUI/mainmenu.cpp
@@ -1,5 +1,6 @@

#include "control.h"
#include "language.h"
#include "DiabloUI/diabloui.h"
#include "DiabloUI/selok.h"

Expand Down
8 changes: 8 additions & 0 deletions Source/diablo.cpp
Expand Up @@ -26,6 +26,7 @@
#include "gmenu.h"
#include "help.h"
#include "init.h"
#include "language.h"
#include "lighting.h"
#include "loadsave.h"
#include "mainmenu.h"
Expand Down Expand Up @@ -188,6 +189,8 @@ static void diablo_parse_flags(int argc, char **argv)
SetPrefPath(argv[++i]);
} else if (strcasecmp("--config-dir", argv[i]) == 0) {
SetConfigPath(argv[++i]);
} else if (strcasecmp("--lang-dir", argv[i]) == 0) {
SetLangPath(argv[++i]);
} else if (strcasecmp("--ttf-dir", argv[i]) == 0) {
SetTtfPath(argv[++i]);
} else if (strcasecmp("--ttf-name", argv[i]) == 0) {
Expand Down Expand Up @@ -517,6 +520,8 @@ static void SaveOptions()
setIniInt("Controller", "Enable Rear Touchpad", sgOptions.Controller.bRearTouch);
#endif

setIniValue("Language", "Code", sgOptions.Language.szCode);

SaveIni();
}

Expand Down Expand Up @@ -591,6 +596,8 @@ static void LoadOptions()
sgOptions.Controller.bRearTouch = getIniBool("Controller", "Enable Rear Touchpad", true);
#endif

getIniValue("Language", "Code", sgOptions.Language.szCode, sizeof(sgOptions.Language.szCode), "en");

sbWasOptionsLoaded = true;
}

Expand Down Expand Up @@ -625,6 +632,7 @@ static void diablo_init()

gbIsHellfireSaveGame = gbIsHellfire;

LanguageInitialize();
UiInitialize();
UiSetSpawned(gbIsSpawn);
was_ui_init = true;
Expand Down
125 changes: 125 additions & 0 deletions Source/language.cpp
@@ -0,0 +1,125 @@
#include "options.h"
#include "utils/paths.h"
#include <map>


using namespace devilution;
#define MO_MAGIC 0x950412de


namespace {

struct cstring_cmp {
bool operator()(const char *s1, const char *s2) const {
return strcmp(s1, s2) < 0;
}
};

std::map<const char*, const char*, cstring_cmp> map;


struct mo_head {
uint32_t magic;
struct {
uint16_t major;
uint16_t minor;
} revision;

uint32_t nb_mappings;
uint32_t src_offset;
uint32_t dst_offset;
};


struct mo_entry {
uint32_t length;
uint32_t offset;
};


void* read_entry(FILE *fp, mo_entry* e) {
void *data;

if(fseek(fp, e->offset, SEEK_SET)) {
return NULL;
}

if(!(data = calloc(e->length, sizeof(char)))) {
return NULL;
}

if(fread(data, sizeof(char), e->length, fp) != e->length) {
free(data);
return NULL;
}

return data;
}
}


const char* LanguageTranslate(const char* key) {
auto it = map.find(key);
if(it == map.end()) {
return key;
}

return it->second;
}


void LanguageInitialize() {
std::string path = GetLangPath() + "./" + sgOptions.Language.szCode + ".mo";
mo_head head;
FILE *fp;

if(!(fp = fopen(path.c_str(), "rb"))) {
perror(path.c_str());
return;
}
// Read header and do sanity checks
if(fread(&head, sizeof(mo_head), 1, fp) != 1) {
return;
}

if(head.magic != MO_MAGIC) {
return; // not a MO file
}

if(head.revision.major > 1 || head.revision.minor > 1) {
return; // unsupported revision
}

// Read entries of source strings
mo_entry src[head.nb_mappings];
if(fseek(fp, head.src_offset, SEEK_SET)) {
return;
}
if(fread(src, sizeof(mo_entry), head.nb_mappings, fp) != head.nb_mappings) {
return;
}

// Read entries of target strings
mo_entry dst[head.nb_mappings];
if(fseek(fp, head.dst_offset, SEEK_SET)) {
return;
}

if(fread(dst, sizeof(mo_entry), head.nb_mappings, fp) != head.nb_mappings) {
return;
}

// Read strings described by entries
for(uint32_t i=0; i<head.nb_mappings; i++) {
void *key, *val;
if((key = read_entry(fp, src+i))) {
if((val = read_entry(fp, dst+i))) {
map[static_cast<const char*>(key)] = static_cast<const char*>(val);
} else {
free(key);
}
}
}
}


7 changes: 7 additions & 0 deletions Source/language.h
@@ -0,0 +1,7 @@
#pragma once

#define _(x) LanguageTranslate(x)

void LanguageInitialize();
const char* LanguageTranslate(const char* key);

6 changes: 6 additions & 0 deletions Source/options.h
Expand Up @@ -124,6 +124,11 @@ struct ChatOptions {
char szHotKeyMsgs[4][MAX_SEND_STR_LEN];
};

struct LanguageOptions {
/** @brief Language code (ISO 639-1) for text. */
char szCode[2];
};

struct Options {
DiabloOptions Diablo;
HellfireOptions Hellfire;
Expand All @@ -133,6 +138,7 @@ struct Options {
ControllerOptions Controller;
NetworkOptions Network;
ChatOptions Chat;
LanguageOptions Language;
};

extern Options sgOptions;
Expand Down
20 changes: 20 additions & 0 deletions Source/utils/paths.cpp
Expand Up @@ -14,13 +14,18 @@
#define TTF_FONT_NAME "CharisSILB.ttf"
#endif

#ifndef MO_LANG_DIR
#define MO_LANG_DIR ""
#endif

namespace devilution {

namespace {

std::string *basePath = NULL;
std::string *prefPath = NULL;
std::string *configPath = NULL;
std::string *langPath = NULL;
std::string *ttfPath = NULL;
std::string *ttfName = NULL;

Expand Down Expand Up @@ -82,6 +87,13 @@ const std::string &GetTtfPath()
return *ttfPath;
}

const std::string &GetLangPath()
{
if (langPath == NULL)
langPath = new std::string(MO_LANG_DIR);
return *langPath;
}

const std::string &GetTtfName()
{
if (ttfName == NULL)
Expand Down Expand Up @@ -113,6 +125,14 @@ void SetConfigPath(const char *path)
AddTrailingSlash(configPath);
}

void SetLangPath(const char *path)
{
if (langPath == NULL)
langPath = new std::string;
*langPath = path;
AddTrailingSlash(langPath);
}

void SetTtfPath(const char *path)
{
if (ttfPath == NULL)
Expand Down
4 changes: 3 additions & 1 deletion Source/utils/paths.h
Expand Up @@ -7,12 +7,14 @@ namespace devilution {
const std::string &GetBasePath();
const std::string &GetPrefPath();
const std::string &GetConfigPath();
const std::string &GetLangPath();
const std::string &GetTtfPath();
const std::string &GetTtfName();

void SetBasePath(const char *path);
void SetPrefPath(const char *path);
void SetConfigPath(const char *path);
void SetLangPath(const char *path);
void SetTtfPath(const char *path);
void SetTtfName(const char *path);

Expand Down

0 comments on commit 8bf129f

Please sign in to comment.