From 05541ed49b8ad5a9988b55c1658e2221c37f5a7a Mon Sep 17 00:00:00 2001 From: HarpyWar <harpywar@gmail.com> Date: Sun, 22 Jun 2014 20:37:06 +0400 Subject: [PATCH] add base for localization https://github.com/HarpyWar/pvpgn/issues/13 --- src/bnetd/CMakeLists.txt | 1 + src/bnetd/command.cpp | 4 +- src/bnetd/i18n.cpp | 236 +++++++++++++++++++++++++++++++++++++ src/bnetd/i18n.h | 58 +++++++++ src/bnetd/luainterface.cpp | 1 + src/bnetd/main.cpp | 4 +- src/bnetd/prefs.cpp | 27 +++++ src/bnetd/prefs.h | 1 + src/bnetd/server.cpp | 6 + src/bnetd/server.h | 1 + src/common/setup_before.h | 1 + 11 files changed, 338 insertions(+), 2 deletions(-) create mode 100644 src/bnetd/i18n.cpp create mode 100644 src/bnetd/i18n.h diff --git a/src/bnetd/CMakeLists.txt b/src/bnetd/CMakeLists.txt index 05ba464..7007acb 100644 --- a/src/bnetd/CMakeLists.txt +++ b/src/bnetd/CMakeLists.txt @@ -36,6 +36,7 @@ set(BNETD_SOURCES anongame_wol.cpp anongame_wol.h handle_wserv.cpp handle_wserv.h luafunctions.cpp luafunctions.h luainterface.cpp luainterface.h luaobjects.cpp luaobjects.h luawrapper.cpp luawrapper.h + i18n.cpp i18n.h ../win32/winmain.cpp ../win32/winmain.h ) diff --git a/src/bnetd/command.cpp b/src/bnetd/command.cpp index e3d3b61..d51d202 100644 --- a/src/bnetd/command.cpp +++ b/src/bnetd/command.cpp @@ -3763,7 +3763,9 @@ namespace pvpgn std::string mode_str = args[1]; if (mode_str == "all") - mode = restart_mode_channels; + mode = restart_mode_all; + else if (mode_str == "i18n") + mode = restart_mode_i18n; else if (mode_str == "channels") mode = restart_mode_channels; else if (mode_str == "realms") diff --git a/src/bnetd/i18n.cpp b/src/bnetd/i18n.cpp new file mode 100644 index 0000000..683562b --- /dev/null +++ b/src/bnetd/i18n.cpp @@ -0,0 +1,236 @@ +/* +* Copyright (C) 2014 HarpyWar (harpywar@gmail.com) +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ +#include "common/setup_before.h" + +#include <algorithm> +#include <cctype> +#include <cstdio> +#include <cerrno> +#include <cstring> +#include <ctime> +#include <cstdlib> +#include <sstream> +#include <iostream> +#include <fstream> +#include <vector> +#include <utility> +#include <map> +#include <string.h> + +#include "compat/strcasecmp.h" +#include "compat/snprintf.h" + +#include "common/token.h" + +#include "common/list.h" +#include "common/eventlog.h" +#include "common/xalloc.h" +#include "common/xstring.h" +#include "common/util.h" +#include "common/tag.h" +#include "common/pugixml.h" +#include "common/format.h" + +#include "account.h" +#include "connection.h" +#include "message.h" +#include "helpfile.h" +#include "channel.h" +#include "i18n.h" +#include "prefs.h" +#include "common/setup_after.h" + +namespace pvpgn +{ + + namespace bnetd + { + const char * commonfile = "common.xml"; // filename template, actually file name is "common-{lang}.xml" + + /* Array with string translations, each string has array with pair language=translation + { + original = { + { language = translate }, + ... + }, + ... + } + */ + std::map<std::string, std::map<t_gamelang, std::string> > translations = std::map<std::string, std::map<t_gamelang, std::string> >(); + + std::vector<t_gamelang> languages{ + GAMELANG_ENGLISH_UINT, /* enUS */ + GAMELANG_GERMAN_UINT, /* deDE */ + GAMELANG_CZECH_UINT, /* csCZ */ + GAMELANG_SPANISH_UINT, /* esES */ + GAMELANG_FRENCH_UINT, /* frFR */ + GAMELANG_ITALIAN_UINT, /* itIT */ + GAMELANG_JAPANESE_UINT, /* jaJA */ + GAMELANG_KOREAN_UINT, /* koKR */ + GAMELANG_POLISH_UINT, /* plPL */ + GAMELANG_RUSSIAN_UINT, /* ruRU */ + GAMELANG_CHINESE_S_UINT, /* zhCN */ + GAMELANG_CHINESE_T_UINT /* zhTW */ + }; + + const char * _find_string(char const * text, t_gamelang gamelang); + + + extern int i18n_reload(void) + { + translations.clear(); + i18n_load(); + + return 0; + } + + extern int i18n_load(void) + { + const char * filename = buildpath(prefs_get_i18ndir(), commonfile); + + std::string lang_filename; + pugi::xml_document doc; + std::string original, translate; + + // iterate language list + for (std::vector<t_gamelang>::iterator lang = languages.begin(); lang != languages.end(); ++lang) + { + lang_filename = i18n_filename(filename, *lang); + if (FILE *f = fopen(lang_filename.c_str(), "r")) { + fclose(f); + + if (!doc.load_file(lang_filename.c_str())) + { + ERROR1("could not parse localization file \"%s\"", lang_filename.c_str()); + continue; + } + } + else { + // file not exists, ignore it + continue; + } + + // root node + pugi::xml_node nodes = doc.child("strings"); + + // load xml strings to map + for (pugi::xml_node node = nodes.child("string"); node; node = node.next_sibling("string")) + { + original = node.child_value("original"); + if (original[0] == '\0') + continue; + + //std::map<const char *, std::map<t_gamelang, const char *> >::iterator it = translations.find(original); + // if not found then init + //if (it == translations.end()) + // translations[original] = std::map<t_gamelang, const char *>(); + + + // check if translate string has a reference to another translation + if (pugi::xml_attribute attr = node.child("translate").attribute("id")) + { + if (pugi::xml_node n = nodes.find_child_by_attribute("id", attr.value())) + translate = n.child_value("translate"); + else + { + translate = original; + WARN2("could not find localization reference id=\"%s\", use original string (%s)", attr.value(), lang_filename.c_str()); + } + } + else + { + translate = node.child_value("translate"); + if (translate[0] == '\0') + { + translate = original; + WARN2("empty localization for \"%s\", use original string (%s)", original, lang_filename.c_str()); + } + } + translations[original][*lang] = translate; + } + + INFO1("localization file loaded \"%s\"", lang_filename.c_str()); + } + + return 0; + } + + /* Find localized text for the given language */ + const char * _find_string(char const * text, t_gamelang gamelang) + { + std::map<std::string, std::map<t_gamelang, std::string> >::iterator it = translations.find(text); + if (it != translations.end()) + { + if (!it->second[gamelang].empty()) + return it->second[gamelang].c_str(); + } + return NULL; + + } + + /* Return text translation */ + extern const char * localize(t_connection * c, char const * text) + { + t_gamelang gamelang = conn_get_gamelang(c); + + if (!gamelang || !tag_check_gamelang(gamelang)) + return text; + + if (const char * translate = _find_string(text, gamelang)) + return translate; + + return text; + } + + + /* Add a locale tag into filename + example: motd.txt -> motd-ruRU.txt */ + extern std::string i18n_filename(const char * filename, t_tag gamelang) + { + // get language string + char lang_str[sizeof(t_tag)+1]; + std::memset(lang_str, 0, sizeof(lang_str)); + tag_uint_to_str(lang_str, gamelang); + + if (!tag_check_gamelang(gamelang)) + { + ERROR1("got unknown language tag \"%s\"", lang_str); + return filename; + } + + std::string _filename(filename); + + // get extension + std::string::size_type idx(_filename.rfind('.')); + if (idx == std::string::npos || idx + 4 != _filename.size()) + { + ERROR1("Invalid extension for '%s'", _filename.c_str()); + return filename; + } + std::string ext(_filename.substr(idx + 1)); + + // get filename without extension + std::string fname(_filename.substr(0, idx)); + + std::string lang_filename(fname + "-" + lang_str + "." + ext); + return lang_filename; + } + + + } +} diff --git a/src/bnetd/i18n.h b/src/bnetd/i18n.h new file mode 100644 index 0000000..18368c0 --- /dev/null +++ b/src/bnetd/i18n.h @@ -0,0 +1,58 @@ +/* +* Copyright (C) 2014 HarpyWar (harpywar@gmail.com) +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ +#ifndef INCLUDED_LOCALIZATION_TYPES +#define INCLUDED_LOCALIZATION_TYPES + +namespace pvpgn +{ + namespace bnetd + { + + + } +} + +#endif + +#ifndef JUST_NEED_TYPES +#ifndef INCLUDED_LOCALIZATION_PROTOS +#define INCLUDED_LOCALIZATION_PROTOS + +#define JUST_NEED_TYPES +# include <string> +#undef JUST_NEED_TYPES + +namespace pvpgn +{ + + namespace bnetd + { + extern int i18n_load(void); + extern int i18n_reload(void); + extern const char * localize(t_connection * c, char const * text); + + + extern std::string i18n_filename(const char * filename, t_tag gamelang); + + } + +} + +/*****/ +#endif +#endif diff --git a/src/bnetd/luainterface.cpp b/src/bnetd/luainterface.cpp index 718a424..ff30364 100644 --- a/src/bnetd/luainterface.cpp +++ b/src/bnetd/luainterface.cpp @@ -167,6 +167,7 @@ namespace pvpgn { lua::table config = bind.table(); config.update("filedir", prefs_get_filedir()); + config.update("i18ndir", prefs_get_i18ndir()); config.update("scriptdir", prefs_get_scriptdir()); config.update("reportdir", prefs_get_reportdir()); config.update("chanlogdir", prefs_get_chanlogdir()); diff --git a/src/bnetd/main.cpp b/src/bnetd/main.cpp index fc118fd..3f9c018 100644 --- a/src/bnetd/main.cpp +++ b/src/bnetd/main.cpp @@ -89,6 +89,7 @@ #include "realm.h" #include "topic.h" #include "handle_apireg.h" +#include "i18n.h" #include "common/setup_after.h" #ifdef WITH_LUA @@ -335,6 +336,8 @@ int pre_server_startup(void) return STATUS_FDWATCH_FAILURE; } + i18n_load(); + connlist_create(); gamelist_create(); timerlist_create(); @@ -382,7 +385,6 @@ int pre_server_startup(void) eventlog(eventlog_level_error, __FUNCTION__, "could not load realm list"); topiclist_load(prefs_get_topicfile()); - #ifdef WITH_LUA lua_load(prefs_get_scriptdir()); #endif diff --git a/src/bnetd/prefs.cpp b/src/bnetd/prefs.cpp index a27ef5c..ce3ec75 100644 --- a/src/bnetd/prefs.cpp +++ b/src/bnetd/prefs.cpp @@ -39,6 +39,7 @@ namespace pvpgn static struct { /* files and paths */ char const * filedir; + char const * i18ndir; char const * storage_path; char const * logfile; char const * loglevels; @@ -179,6 +180,10 @@ namespace pvpgn static const char *conf_get_filedir(void); static int conf_setdef_filedir(void); + static int conf_set_i18ndir(const char *valstr); + static const char *conf_get_i18ndir(void); + static int conf_setdef_i18ndir(void); + static int conf_set_storage_path(const char *valstr); static const char *conf_get_storage_path(void); static int conf_setdef_storage_path(void); @@ -703,6 +708,7 @@ namespace pvpgn static t_conf_entry conf_table[] = { { "filedir", conf_set_filedir, conf_get_filedir, conf_setdef_filedir }, + { "i18ndir", conf_set_i18ndir, conf_get_i18ndir, conf_setdef_i18ndir }, { "storage_path", conf_set_storage_path, conf_get_storage_path, conf_setdef_storage_path }, { "logfile", conf_set_logfile, conf_get_logfile, conf_setdef_logfile }, { "loglevels", conf_set_loglevels, conf_get_loglevels, conf_setdef_loglevels }, @@ -912,6 +918,27 @@ namespace pvpgn } + extern char const * prefs_get_i18ndir(void) + { + return prefs_runtime_config.i18ndir; + } + + static int conf_set_i18ndir(const char *valstr) + { + return conf_set_str(&prefs_runtime_config.i18ndir, valstr, NULL); + } + + static int conf_setdef_i18ndir(void) + { + return conf_set_str(&prefs_runtime_config.i18ndir, NULL, BNETD_I18N_DIR); + } + + static const char* conf_get_i18ndir(void) + { + return prefs_runtime_config.i18ndir; + } + + extern char const * prefs_get_logfile(void) { return prefs_runtime_config.logfile; diff --git a/src/bnetd/prefs.h b/src/bnetd/prefs.h index 46d3bc1..7655677 100644 --- a/src/bnetd/prefs.h +++ b/src/bnetd/prefs.h @@ -37,6 +37,7 @@ namespace pvpgn extern void prefs_unload(void); extern char const * prefs_get_storage_path(void); extern char const * prefs_get_filedir(void); + extern char const * prefs_get_i18ndir(void); extern char const * prefs_get_logfile(void); extern char const * prefs_get_loglevels(void); extern char const * prefs_get_motdfile(void); diff --git a/src/bnetd/server.cpp b/src/bnetd/server.cpp index 31ef3de..0533503 100644 --- a/src/bnetd/server.cpp +++ b/src/bnetd/server.cpp @@ -86,6 +86,7 @@ #include "icons.h" #include "anongame_infos.h" #include "topic.h" +#include "i18n.h" #include "common/setup_after.h" #ifdef WITH_LUA @@ -1373,6 +1374,11 @@ namespace pvpgn attrlayer_load_default(); } + if (do_restart == restart_mode_all || do_restart == restart_mode_i18n) + { + i18n_reload(); + } + if (do_restart == restart_mode_all || do_restart == restart_mode_channels) { channellist_reload(); diff --git a/src/bnetd/server.h b/src/bnetd/server.h index 6723efe..b5a763a 100644 --- a/src/bnetd/server.h +++ b/src/bnetd/server.h @@ -76,6 +76,7 @@ namespace pvpgn restart_mode_none, // always first (0) restart_mode_all, + restart_mode_i18n, restart_mode_channels, restart_mode_realms, restart_mode_autoupdate, diff --git a/src/common/setup_before.h b/src/common/setup_before.h index bf37070..73daa66 100644 --- a/src/common/setup_before.h +++ b/src/common/setup_before.h @@ -126,6 +126,7 @@ const bool BNETD_CHANLOG = false; # define BNETD_DEFAULT_CONF_FILE "conf/bnetd.conf" #endif const char * const BNETD_FILE_DIR = "files"; +const char * const BNETD_I18N_DIR = "conf/i18n"; const char * const BNETD_SCRIPT_DIR = "lua"; const char * const BNETD_STORAGE_PATH = ""; const char * const BNETD_REPORT_DIR = "reports";