diff --git a/Makefile b/Makefile index 7ff74787..40e72918 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ I2PD := i2pd LIB_SRC_DIR := libi2pd LIB_CLIENT_SRC_DIR := libi2pd_client +LANG_SRC_DIR := i18n DAEMON_SRC_DIR := daemon # import source files lists @@ -49,12 +50,13 @@ ifeq ($(USE_MESHNET),yes) NEEDED_CXXFLAGS += -DMESHNET endif -NEEDED_CXXFLAGS += -MMD -MP -I$(LIB_SRC_DIR) -I$(LIB_CLIENT_SRC_DIR) +NEEDED_CXXFLAGS += -MMD -MP -I$(LIB_SRC_DIR) -I$(LIB_CLIENT_SRC_DIR) -I$(LANG_SRC_DIR) LIB_OBJS += $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) LIB_CLIENT_OBJS += $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC)) +LANG_OBJS += $(patsubst %.cpp,obj/%.o,$(LANG_SRC)) DAEMON_OBJS += $(patsubst %.cpp,obj/%.o,$(DAEMON_SRC)) -DEPS += $(LIB_OBJS:.o=.d) $(LIB_CLIENT_OBJS:.o=.d) $(DAEMON_OBJS:.o=.d) +DEPS += $(LIB_OBJS:.o=.d) $(LIB_CLIENT_OBJS:.o=.d) $(LANG_OBJS:.o=.d) $(DAEMON_OBJS:.o=.d) all: mk_obj_dir $(ARLIB) $(ARLIB_CLIENT) $(I2PD) @@ -63,6 +65,7 @@ mk_obj_dir: @mkdir -p obj/Win32 @mkdir -p obj/$(LIB_SRC_DIR) @mkdir -p obj/$(LIB_CLIENT_SRC_DIR) + @mkdir -p obj/$(LANG_SRC_DIR) @mkdir -p obj/$(DAEMON_SRC_DIR) api: mk_obj_dir $(SHLIB) $(ARLIB) @@ -82,7 +85,7 @@ obj/%.o: %.cpp # '-' is 'ignore if missing' on first run -include $(DEPS) -$(I2PD): $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT) +$(I2PD): $(LANG_OBJS) $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT) $(CXX) -o $@ $(LDFLAGS) $^ $(LDLIBS) $(SHLIB): $(LIB_OBJS) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index d56c2894..043d1630 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -30,6 +30,12 @@ #include "Daemon.h" #include "util.h" #include "ECIESX25519AEADRatchetSession.h" +#include "I18N.h" + +#ifdef _WIN32 +#include +#include +#endif #ifdef WIN32_APP #include "Win32App.h" @@ -130,23 +136,37 @@ namespace http { static std::string ConvertTime (uint64_t time); std::map HTTPConnection::m_Tokens; + std::string DataPath; + + static void SetDataDir () + { +#ifdef _WIN32 + boost::filesystem::wpath path (i2p::fs::GetDataDir()); + auto loc = boost::filesystem::path::imbue(std::locale( std::locale(), new std::codecvt_utf8_utf16() ) ); + i2p::http::DataPath = path.string(); + boost::filesystem::path::imbue(loc); // Return it back +#else + i2p::http::DataPath = i2p::fs::GetDataDir(); +#endif + } + static void ShowUptime (std::stringstream& s, int seconds) { int num; if ((num = seconds / 86400) > 0) { - s << num << " days, "; + s << num << " " << tr("days", num) << ", "; seconds -= num * 86400; } if ((num = seconds / 3600) > 0) { - s << num << " hours, "; + s << num << " " << tr("hours", num) << ", "; seconds -= num * 3600; } if ((num = seconds / 60) > 0) { - s << num << " min, "; + s << num << " " << tr("minutes", num) << ", "; seconds -= num * 60; } - s << seconds << " seconds"; + s << seconds << " " << tr("seconds", seconds); } static void ShowTraffic (std::stringstream& s, uint64_t bytes) @@ -197,11 +217,7 @@ namespace http { "\r\n" "\r\n" /* TODO: Add support for locale */ " \r\n" /* TODO: Find something to parse html/template system. This is horrible. */ -#if (!defined(WIN32)) " \r\n" -#else - " \r\n" -#endif " \r\n" " \r\n" " Purple I2P " VERSION " Webconsole\r\n" @@ -315,7 +331,7 @@ namespace http { s << "Transit: "; ShowTraffic (s, i2p::transport::transports.GetTotalTransitTransmittedBytes ()); s << " (" << (double) i2p::transport::transports.GetTransitBandwidth () / 1024 << " KiB/s)
\r\n"; - s << "Data path: " << i2p::fs::GetDataDir() << "
\r\n"; + s << "Data path: " << i2p::http::DataPath << "
\r\n"; s << "
"; if((outputFormat==OutputFormatEnum::forWebConsole)||!includeHiddenContent) { s << "\r\n\r\n
\r\n"; @@ -1374,6 +1390,8 @@ namespace http { LogPrint(eLogInfo, "HTTPServer: password set to ", pass); } + i2p::http::SetDataDir(); + m_IsRunning = true; m_Thread.reset (new std::thread (std::bind (&HTTPServer::Run, this))); m_Acceptor.listen (); diff --git a/filelist.mk b/filelist.mk index 7d13fb2f..e2a5d40e 100644 --- a/filelist.mk +++ b/filelist.mk @@ -19,4 +19,6 @@ LIB_CLIENT_SRC = $(wildcard $(LIB_CLIENT_SRC_DIR)/*.cpp) #DAEMON_SRC = \ # HTTPServer.cpp I2PControl.cpp UPnP.cpp Daemon.cpp i2pd.cpp +LANG_SRC = $(wildcard $(LANG_SRC_DIR)/*.cpp) + DAEMON_SRC = $(wildcard $(DAEMON_SRC_DIR)/*.cpp) diff --git a/i18n/I18N.h b/i18n/I18N.h new file mode 100644 index 00000000..4556fe25 --- /dev/null +++ b/i18n/I18N.h @@ -0,0 +1,39 @@ +/* +* Copyright (c) 2021, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#ifndef __I18N_H__ +#define __I18N_H__ + +namespace i2p { +namespace i18n { + + namespace russian { + std::string GetString (std::string arg); + std::string GetPlural (std::string arg, int n); + } + + std::string translate (std::string arg) + { + return i2p::i18n::russian::GetString (arg); + } + + template + std::string translate (std::string arg, inttype&& n) + { + return i2p::i18n::russian::GetPlural (arg, (int) n); + } + +} // i18n +} // i2p + +template +std::string tr (TArgs&&... args) { + return i2p::i18n::translate(std::forward(args)...); +} + +#endif // __I18N_H__ diff --git a/i18n/russian.cpp b/i18n/russian.cpp new file mode 100644 index 00000000..892ee203 --- /dev/null +++ b/i18n/russian.cpp @@ -0,0 +1,56 @@ +#include +#include +#include + +// Russian localization file + +namespace i2p { +namespace i18n { +namespace russian { // language + + // See for language plural forms here: + // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html + int plural (int n) { + return n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2; + } + + static std::map strings + { + {"Enabled", "Включено"}, + {"Disabled", "Выключено"} + }; + + static std::map> plurals + { + {"days", {"день", "дня", "дней"}}, + {"hours", {"час", "часа", "часов"}}, + {"minutes", {"минута", "минуты", "минут"}}, + {"seconds", {"секунда", "секунды", "секунд"}} + }; + + std::string GetString (std::string arg) + { + auto it = strings.find(arg); + if (it == strings.end()) + { + return arg; + } else { + return it->second; + } + } + + std::string GetPlural (std::string arg, int n) + { + auto it = plurals.find(arg); + if (it == plurals.end()) + { + return arg; + } else { + int form = plural(n); + return it->second[form]; + } + } + +} // language +} // i18n +} // i2p diff --git a/libi2pd/FS.cpp b/libi2pd/FS.cpp index bd1a7ad2..250b24ef 100644 --- a/libi2pd/FS.cpp +++ b/libi2pd/FS.cpp @@ -47,10 +47,10 @@ namespace fs { return; } #ifdef _WIN32 - char localAppData[MAX_PATH]; + wchar_t localAppData[MAX_PATH]; // check executable directory first - if(!GetModuleFileName(NULL, localAppData, MAX_PATH)) + if(!GetModuleFileNameW(NULL, localAppData, MAX_PATH)) { #ifdef WIN32_APP MessageBox(NULL, TEXT("Unable to get application path!"), TEXT("I2Pd: error"), MB_ICONERROR | MB_OK); @@ -61,14 +61,15 @@ namespace fs { } else { - auto execPath = boost::filesystem::path(localAppData).parent_path(); + auto execPath = boost::filesystem::wpath(localAppData).parent_path(); // if config file exists in .exe's folder use it if(boost::filesystem::exists(execPath/"i2pd.conf")) // TODO: magic string + { dataDir = execPath.string (); - else // otherwise %appdata% + } else // otherwise %appdata% { - if(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, localAppData) != S_OK) + if(SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, 0, localAppData) != S_OK) { #ifdef WIN32_APP MessageBox(NULL, TEXT("Unable to get AppData path!"), TEXT("I2Pd: error"), MB_ICONERROR | MB_OK); @@ -78,7 +79,9 @@ namespace fs { exit(1); } else - dataDir = std::string(localAppData) + "\\" + appName; + { + dataDir = boost::filesystem::wpath(localAppData).string() + "\\" + appName; + } } } return;