From 0e68fe4a57bd782faf9ef59e77a7d683b3dac8d2 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sat, 22 May 2021 08:39:05 +0300 Subject: [PATCH 1/6] [i18n] start multilang support for webconsole Signed-off-by: R4SAS --- Makefile | 9 ++++--- daemon/HTTPServer.cpp | 36 +++++++++++++++++++++------- filelist.mk | 2 ++ i18n/I18N.h | 39 ++++++++++++++++++++++++++++++ i18n/russian.cpp | 56 +++++++++++++++++++++++++++++++++++++++++++ libi2pd/FS.cpp | 15 +++++++----- 6 files changed, 139 insertions(+), 18 deletions(-) create mode 100644 i18n/I18N.h create mode 100644 i18n/russian.cpp 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; From 80b44fc9a9c6a90872ccaa1988737bda67f73e3f Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sat, 22 May 2021 18:29:05 +0300 Subject: [PATCH 2/6] Support multilang, update code Signed-off-by: R4SAS --- contrib/i2pd.conf | 3 +++ daemon/Daemon.cpp | 7 +++++ daemon/HTTPServer.cpp | 23 +--------------- i18n/English.cpp | 56 +++++++++++++++++++++++++++++++++++++++ i18n/I18N.h | 21 +++++++++++++-- libi2pd/Config.cpp | 1 + libi2pd/FS.cpp | 13 +++++++++ libi2pd/FS.h | 3 +++ libi2pd/RouterContext.cpp | 7 ++++- libi2pd/RouterContext.h | 18 ++++++++++--- 10 files changed, 124 insertions(+), 28 deletions(-) create mode 100644 i18n/English.cpp diff --git a/contrib/i2pd.conf b/contrib/i2pd.conf index e8c397f5..3c9c71ff 100644 --- a/contrib/i2pd.conf +++ b/contrib/i2pd.conf @@ -103,6 +103,9 @@ port = 7070 # auth = true # user = i2pd # pass = changeme +## Select webconsole language +## Currently supported only english (default) and russian languages +# lang = english [httpproxy] ## Uncomment and set to 'false' to disable HTTP Proxy diff --git a/daemon/Daemon.cpp b/daemon/Daemon.cpp index 77000652..ec62f5d6 100644 --- a/daemon/Daemon.cpp +++ b/daemon/Daemon.cpp @@ -343,6 +343,13 @@ namespace util LogPrint(eLogInfo, "Daemon: using hidden mode"); i2p::data::netdb.SetHidden(true); } + + std::string httpLang; i2p::config::GetOption("http.lang", httpLang); + if (!httpLang.compare("russian")) + i2p::context.SetLanguage (eRussian); + else + i2p::context.SetLanguage (eEnglish); + return true; } diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 043d1630..87744f37 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -32,11 +32,6 @@ #include "ECIESX25519AEADRatchetSession.h" #include "I18N.h" -#ifdef _WIN32 -#include -#include -#endif - #ifdef WIN32_APP #include "Win32App.h" #endif @@ -136,20 +131,6 @@ 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; @@ -331,7 +312,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::http::DataPath << "
\r\n"; + s << "Data path: " << i2p::fs::GetUTF8DataDir() << "
\r\n"; s << "
"; if((outputFormat==OutputFormatEnum::forWebConsole)||!includeHiddenContent) { s << "\r\n\r\n
\r\n"; @@ -1390,8 +1371,6 @@ 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/i18n/English.cpp b/i18n/English.cpp new file mode 100644 index 00000000..8664a0f0 --- /dev/null +++ b/i18n/English.cpp @@ -0,0 +1,56 @@ +#include +#include +#include + +// Russian localization file + +namespace i2p { +namespace i18n { +namespace english { // language + + // See for language plural forms here: + // https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html + int plural (int n) { + return n != 1 ? 1 : 0; + } + + static std::map strings + { + {"Enabled", "Enabled"}, + {"Disabled", "Disabled"} + }; + + static std::map> plurals + { + {"days", {"day", "days"}}, + {"hours", {"hour", "hours"}}, + {"minutes", {"minute", "minutes"}}, + {"seconds", {"second", "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/i18n/I18N.h b/i18n/I18N.h index 4556fe25..eddec514 100644 --- a/i18n/I18N.h +++ b/i18n/I18N.h @@ -9,9 +9,16 @@ #ifndef __I18N_H__ #define __I18N_H__ +#include "RouterContext.h" + namespace i2p { namespace i18n { + namespace english { + std::string GetString (std::string arg); + std::string GetPlural (std::string arg, int n); + } + namespace russian { std::string GetString (std::string arg); std::string GetPlural (std::string arg, int n); @@ -19,13 +26,23 @@ namespace i18n { std::string translate (std::string arg) { - return i2p::i18n::russian::GetString (arg); + switch (i2p::context.GetLanguage ()) + { + case eEnglish: return i2p::i18n::english::GetString (arg); + case eRussian: return i2p::i18n::russian::GetString (arg); + default: return arg; + } } template std::string translate (std::string arg, inttype&& n) { - return i2p::i18n::russian::GetPlural (arg, (int) n); + switch (i2p::context.GetLanguage ()) + { + case eEnglish: return i2p::i18n::english::GetPlural (arg, (int) n); + case eRussian: return i2p::i18n::russian::GetPlural (arg, (int) n); + default: return arg; + } } } // i18n diff --git a/libi2pd/Config.cpp b/libi2pd/Config.cpp index 5b584e35..f5316860 100644 --- a/libi2pd/Config.cpp +++ b/libi2pd/Config.cpp @@ -93,6 +93,7 @@ namespace config { ("http.strictheaders", value()->default_value(true), "Enable strict host checking on WebUI") ("http.hostname", value()->default_value("localhost"), "Expected hostname for WebUI") ("http.webroot", value()->default_value("/"), "WebUI root path (default: / )") + ("http.lang", value()->default_value("english"), "WebUI language (default: english )") ; options_description httpproxy("HTTP Proxy options"); diff --git a/libi2pd/FS.cpp b/libi2pd/FS.cpp index 250b24ef..6ac302b0 100644 --- a/libi2pd/FS.cpp +++ b/libi2pd/FS.cpp @@ -12,6 +12,7 @@ #ifdef _WIN32 #include #include +#include #endif #include "Base.h" @@ -41,6 +42,18 @@ namespace fs { return dataDir; } + const std::string GetUTF8DataDir () { +#ifdef _WIN32 + boost::filesystem::wpath path (dataDir); + auto loc = boost::filesystem::path::imbue(std::locale( std::locale(), new std::codecvt_utf8_utf16() ) ); // convert path to UTF-8 + auto dataDirUTF8 = path.string(); + boost::filesystem::path::imbue(loc); // Return locale settings back + return dataDirUTF8; +#else + return dataDir; // linux, osx, android uses UTF-8 by default +#endif + } + void DetectDataDir(const std::string & cmdline_param, bool isService) { if (cmdline_param != "") { dataDir = cmdline_param; diff --git a/libi2pd/FS.h b/libi2pd/FS.h index 698e9b6b..f07ee35c 100644 --- a/libi2pd/FS.h +++ b/libi2pd/FS.h @@ -75,6 +75,9 @@ namespace fs { /** @brief Returns datadir path */ const std::string & GetDataDir(); + /** @brief Returns datadir path in UTF-8 encoding */ + const std::string GetUTF8DataDir(); + /** * @brief Set datadir either from cmdline option or using autodetection * @param cmdline_param Value of cmdline parameter --datadir= diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index ad6576ed..0cfcb18a 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -29,7 +29,7 @@ namespace i2p RouterContext::RouterContext (): m_LastUpdateTime (0), m_AcceptsTunnels (true), m_IsFloodfill (false), m_ShareRatio (100), m_Status (eRouterStatusUnknown), m_StatusV6 (eRouterStatusUnknown), - m_Error (eRouterErrorNone), m_NetID (I2PD_NET_ID) + m_Error (eRouterErrorNone), m_NetID (I2PD_NET_ID), m_Language (eEnglish) { } @@ -916,6 +916,11 @@ namespace i2p } } + void RouterContext::SetLanguage (Lang language) + { + m_Language = language; + } + i2p::crypto::X25519Keys& RouterContext::GetStaticKeys () { if (!m_StaticKeys) diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index a4d18f82..dad3fdca 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -24,7 +24,7 @@ namespace i2p namespace garlic { class RouterIncomingRatchetSession; -} +} const char ROUTER_INFO[] = "router.info"; const char ROUTER_KEYS[] = "router.keys"; @@ -39,7 +39,7 @@ namespace garlic eRouterStatusError = 3, eRouterStatusUnknown = 4, eRouterStatusProxy = 5, - eRouterStatusMesh = 6 + eRouterStatusMesh = 6 }; enum RouterError @@ -49,7 +49,12 @@ namespace garlic eRouterErrorOffline = 2, eRouterErrorSymmetricNAT = 3 }; - + + enum Lang { + eEnglish = 0, + eRussian + }; + class RouterContext: public i2p::garlic::GarlicDestination { private: @@ -144,6 +149,10 @@ namespace garlic void ProcessGarlicMessage (std::shared_ptr msg); void ProcessDeliveryStatusMessage (std::shared_ptr msg); + // i18n + Lang GetLanguage () const { return m_Language; }; + void SetLanguage (Lang language); + protected: // implements GarlicDestination @@ -178,6 +187,9 @@ namespace garlic std::unique_ptr m_StaticKeys; // for ECIESx25519 std::unique_ptr m_InitialNoiseState, m_CurrentNoiseState; + + // i18n + Lang m_Language; }; extern RouterContext context; From df66c2d2dcc55355c9812e6b8cf265a753cd4595 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 23 May 2021 06:06:04 +0300 Subject: [PATCH 3/6] [i18n] translate HTTP proxy Signed-off-by: R4SAS --- i18n/I18N.h | 16 ++------ i18n/I18N_langs.h | 34 ++++++++++++++++ i18n/russian.cpp | 43 +++++++++++++++++++-- libi2pd/HTTP.cpp | 2 + libi2pd/RouterContext.h | 6 +-- libi2pd_client/HTTPProxy.cpp | 75 +++++++++++++++++++----------------- 6 files changed, 120 insertions(+), 56 deletions(-) create mode 100644 i18n/I18N_langs.h diff --git a/i18n/I18N.h b/i18n/I18N.h index eddec514..cb3e5c1c 100644 --- a/i18n/I18N.h +++ b/i18n/I18N.h @@ -11,20 +11,11 @@ #include "RouterContext.h" + namespace i2p { namespace i18n { - namespace english { - std::string GetString (std::string arg); - std::string GetPlural (std::string arg, int n); - } - - namespace russian { - std::string GetString (std::string arg); - std::string GetPlural (std::string arg, int n); - } - - std::string translate (std::string arg) + inline std::string translate (std::string arg) { switch (i2p::context.GetLanguage ()) { @@ -49,7 +40,8 @@ namespace i18n { } // i2p template -std::string tr (TArgs&&... args) { +std::string tr (TArgs&&... args) +{ return i2p::i18n::translate(std::forward(args)...); } diff --git a/i18n/I18N_langs.h b/i18n/I18N_langs.h new file mode 100644 index 00000000..24c683b4 --- /dev/null +++ b/i18n/I18N_langs.h @@ -0,0 +1,34 @@ +/* +* 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_LANGS_H__ +#define __I18N_LANGS_H__ + +namespace i2p { + +enum Lang { + eEnglish = 0, + eRussian +}; + +namespace i18n { + + namespace english { + std::string GetString (std::string arg); + std::string GetPlural (std::string arg, int n); + } + + namespace russian { + std::string GetString (std::string arg); + std::string GetPlural (std::string arg, int n); + } + +} // i18n +} // i2p + +#endif // __I18N_LANGS_H__ diff --git a/i18n/russian.cpp b/i18n/russian.cpp index 892ee203..feb6fc63 100644 --- a/i18n/russian.cpp +++ b/i18n/russian.cpp @@ -16,8 +16,44 @@ namespace russian { // language static std::map strings { - {"Enabled", "Включено"}, - {"Disabled", "Выключено"} + // HTTP Proxy + {"Proxy error", "Ошибка прокси"}, + {"Proxy info", "Информация прокси"}, + {"Proxy error: Host not found", "Ошибка прокси: Адрес не найден"}, + {"Remote host not found in router's addressbook", "Запрошенный адрес не найден в адресной книге роутера"}, + {"You may try to find this host on jump services below", "Вы можете попробовать найти адрес на джамп сервисах ниже"}, + {"Invalid request", "Некорректный запрос"}, + {"Proxy unable to parse your request", "Прокси не может разобрать ваш запрос"}, + {"addresshelper is not supported", "addresshelper не поддерживается"}, + {"Host", "Адрес"}, + {"added to router's addressbook from helper", "добавлен в адресную книгу роутера через хелпер"}, + {"already in router's addressbook", "уже а адресной книге роутера"}, + {"Click", "Нажмите"}, + {"here", "здесь"}, + {"to proceed", "чтобы продолжить"}, + {"to update record", "чтобы обновить запись"}, + {"Addresshelper found", "Найден addresshelper"}, + {"invalid request uri", "некорректный URI запроса"}, + {"Can't detect destination host from request", "Не удалось определить адрес назначения из запроса"}, + {"Outproxy failure", "Ошибка внешнего прокси"}, + {"bad outproxy settings", "некорректные настройки внешнего прокси"}, + {"not inside I2P network, but outproxy is not enabled", "не в I2P сети, но внешний прокси не включен"}, + {"unknown outproxy url", "неизвестный URL внешнего прокси"}, + {"cannot resolve upstream proxy", "не удается определить внешний прокси"}, + {"hostname too long", "имя хоста слишком длинное"}, + {"cannot connect to upstream socks proxy", "не удается подключиться к вышестоящему SOCKS прокси"}, + {"Cannot negotiate with socks proxy", "Не удается договориться с вышестоящим SOCKS прокси"}, + {"CONNECT error", "Ошибка CONNECT запроса"}, + {"Failed to Connect", "Не удалось подключиться"}, + {"socks proxy error", "ошибка SOCKS прокси"}, + {"failed to send request to upstream", "не удалось отправить запрос вышестоящему прокси"}, + {"No Reply From socks proxy", "Нет ответа от SOCKS прокси сервера"}, + {"cannot connect", "не удалось подключиться"}, + {"http out proxy not implemented", "поддержка внешнего HTTP прокси сервера не реализована"}, + {"cannot connect to upstream http proxy", "не удалось подключиться к вышестоящему HTTP прокси серверу"}, + {"Host is down", "Адрес недоступен"}, + {"Can't create connection to requested host, it may be down. Please try again later.", "Не удалось установить соединение к запрошенному адресу, возможно он не в сети. Попробуйте повторить запрос позже."}, + {"", ""}, }; static std::map> plurals @@ -25,7 +61,8 @@ namespace russian { // language {"days", {"день", "дня", "дней"}}, {"hours", {"час", "часа", "часов"}}, {"minutes", {"минута", "минуты", "минут"}}, - {"seconds", {"секунда", "секунды", "секунд"}} + {"seconds", {"секунда", "секунды", "секунд"}}, + {"", {"", ""}}, }; std::string GetString (std::string arg) diff --git a/libi2pd/HTTP.cpp b/libi2pd/HTTP.cpp index da4299e9..6ad245f0 100644 --- a/libi2pd/HTTP.cpp +++ b/libi2pd/HTTP.cpp @@ -187,6 +187,8 @@ namespace http params.clear(); for (const auto& it : tokens) { + if (!it.length()) // empty + continue; std::size_t eq = it.find ('='); if (eq != std::string::npos) { auto e = std::pair(it.substr(0, eq), it.substr(eq + 1)); diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index dad3fdca..1cb77a74 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -18,6 +18,7 @@ #include "Identity.h" #include "RouterInfo.h" #include "Garlic.h" +#include "I18N_langs.h" namespace i2p { @@ -50,11 +51,6 @@ namespace garlic eRouterErrorSymmetricNAT = 3 }; - enum Lang { - eEnglish = 0, - eRussian - }; - class RouterContext: public i2p::garlic::GarlicDestination { private: diff --git a/libi2pd_client/HTTPProxy.cpp b/libi2pd_client/HTTPProxy.cpp index 6b2b8df7..aff165b0 100644 --- a/libi2pd_client/HTTPProxy.cpp +++ b/libi2pd_client/HTTPProxy.cpp @@ -28,6 +28,7 @@ #include "I2PTunnel.h" #include "Config.h" #include "HTTP.h" +#include "I18N.h" namespace i2p { namespace proxy { @@ -71,8 +72,8 @@ namespace proxy { void SentHTTPFailed(const boost::system::error_code & ecode); void HandleStreamRequestComplete (std::shared_ptr stream); /* error helpers */ - void GenericProxyError(const char *title, const char *description); - void GenericProxyInfo(const char *title, const char *description); + void GenericProxyError(const std::string& title, const std::string& description); + void GenericProxyInfo(const std::string& title, const std::string& description); void HostNotFound(std::string & host); void SendProxyError(std::string & content); @@ -151,17 +152,17 @@ namespace proxy { Done(shared_from_this()); } - void HTTPReqHandler::GenericProxyError(const char *title, const char *description) { + void HTTPReqHandler::GenericProxyError(const std::string& title, const std::string& description) { std::stringstream ss; - ss << "

Proxy error: " << title << "

\r\n"; + ss << "

" << tr("Proxy error") << ": " << title << "

\r\n"; ss << "

" << description << "

\r\n"; std::string content = ss.str(); SendProxyError(content); } - void HTTPReqHandler::GenericProxyInfo(const char *title, const char *description) { + void HTTPReqHandler::GenericProxyInfo(const std::string& title, const std::string& description) { std::stringstream ss; - ss << "

Proxy info: " << title << "

\r\n"; + ss << "

" << tr("Proxy info") << ": " << title << "

\r\n"; ss << "

" << description << "

\r\n"; std::string content = ss.str(); SendProxyError(content); @@ -169,9 +170,9 @@ namespace proxy { void HTTPReqHandler::HostNotFound(std::string & host) { std::stringstream ss; - ss << "

Proxy error: Host not found

\r\n" - << "

Remote host not found in router's addressbook

\r\n" - << "

You may try to find this host on jump services below:

\r\n" + ss << "

" << tr("Proxy error: Host not found") << "

\r\n" + << "

" << tr("Remote host not found in router's addressbook") << "

\r\n" + << "

" << tr("You may try to find this host on jump services below") << ":

\r\n" << "
    \r\n"; for (const auto& js : jumpservices) { ss << "
  • " << js.first << "
  • \r\n"; @@ -216,7 +217,7 @@ namespace proxy { b64 = i2p::http::UrlDecode(value); // if we need update exists, request formed with update param if (params["update"] == "true") { len += std::strlen("&update=true"); confirm = true; } - url.query.replace(pos, len, ""); + url.query.replace(pos - 1, len + 1, ""); // +-1 for taking ? and & before parameter return true; } @@ -268,7 +269,7 @@ namespace proxy { if (m_req_len < 0) { LogPrint(eLogError, "HTTPProxy: unable to parse request"); - GenericProxyError("Invalid request", "Proxy unable to parse your request"); + GenericProxyError(tr("Invalid request"), tr("Proxy unable to parse your request")); return true; /* parse error */ } @@ -283,7 +284,7 @@ namespace proxy { if (!m_Addresshelper) { LogPrint(eLogWarning, "HTTPProxy: addresshelper request rejected"); - GenericProxyError("Invalid request", "addresshelper is not supported"); + GenericProxyError(tr("Invalid request"), tr("addresshelper is not supported")); return true; } if (!i2p::client::context.GetAddressBook ().FindAddress (m_RequestURL.host) || m_Confirm) @@ -292,17 +293,19 @@ namespace proxy { LogPrint (eLogInfo, "HTTPProxy: added address from addresshelper for ", m_RequestURL.host); std::string full_url = m_RequestURL.to_string(); std::stringstream ss; - ss << "Host " << m_RequestURL.host << " added to router's addressbook from helper. " - << "Click here to proceed."; - GenericProxyInfo("Addresshelper found", ss.str().c_str()); + ss << tr("Host") <<" " << m_RequestURL.host << " " << tr("added to router's addressbook from helper") << ". "; + ss << tr("Click") << " " << tr("here") << " " << tr("to proceed") << "."; + GenericProxyInfo(tr("Addresshelper found"), ss.str()); return true; /* request processed */ } else { + std::string full_url = m_RequestURL.to_string(); std::stringstream ss; - ss << "Host " << m_RequestURL.host << " already in router's addressbook. " - << "Click here to update record."; - GenericProxyInfo("Addresshelper found", ss.str().c_str()); + ss << tr("Host") << " " << m_RequestURL.host << " " << tr("already in router's addressbook") << ". "; + ss << tr("Click") << " " << tr("here") << " " << tr("to update record") << "."; + GenericProxyInfo(tr("Addresshelper found"), ss.str()); return true; /* request processed */ } } @@ -315,7 +318,7 @@ namespace proxy { auto pos = uri.find(":"); if(pos == std::string::npos || pos == uri.size() - 1) { - GenericProxyError("Invalid Request", "invalid request uri"); + GenericProxyError(tr("Invalid Request"), tr("invalid request uri")); return true; } else @@ -358,7 +361,7 @@ namespace proxy { else { /* relative url and missing 'Host' header */ - GenericProxyError("Invalid request", "Can't detect destination host from request"); + GenericProxyError(tr("Invalid request"), tr("Can't detect destination host from request")); return true; } } @@ -375,11 +378,11 @@ namespace proxy { if(m_ProxyURL.parse(m_OutproxyUrl)) ForwardToUpstreamProxy(); else - GenericProxyError("Outproxy failure", "bad outproxy settings"); + GenericProxyError(tr("Outproxy failure"), tr("bad outproxy settings")); } else { LogPrint (eLogWarning, "HTTPProxy: outproxy failure for ", dest_host, ": no outproxy enabled"); - std::string message = "Host " + dest_host + " not inside I2P network, but outproxy is not enabled"; - GenericProxyError("Outproxy failure", message.c_str()); + std::stringstream ss; ss << tr("Host") << " " << dest_host << " " << tr("not inside I2P network, but outproxy is not enabled"); + GenericProxyError(tr("Outproxy failure"), ss.str()); } return true; } @@ -467,13 +470,13 @@ namespace proxy { else { // unknown type, complain - GenericProxyError("unknown outproxy url", m_ProxyURL.to_string().c_str()); + GenericProxyError(tr("unknown outproxy url"), m_ProxyURL.to_string()); } } void HTTPReqHandler::HandleUpstreamProxyResolved(const boost::system::error_code & ec, boost::asio::ip::tcp::resolver::iterator it, ProxyResolvedHandler handler) { - if(ec) GenericProxyError("cannot resolve upstream proxy", ec.message().c_str()); + if(ec) GenericProxyError(tr("cannot resolve upstream proxy"), ec.message()); else handler(*it); } @@ -481,7 +484,7 @@ namespace proxy { { if(!ec) { if(m_RequestURL.host.size() > 255) { - GenericProxyError("hostname too long", m_RequestURL.host.c_str()); + GenericProxyError(tr("hostname too long"), m_RequestURL.host); return; } uint16_t port = m_RequestURL.port; @@ -508,13 +511,13 @@ namespace proxy { reqsize += host.size(); m_socks_buf[++reqsize] = 0; boost::asio::async_write(*m_proxysock, boost::asio::buffer(m_socks_buf, reqsize), boost::asio::transfer_all(), std::bind(&HTTPReqHandler::HandleSocksProxySendHandshake, this, std::placeholders::_1, std::placeholders::_2)); - } else GenericProxyError("cannot connect to upstream socks proxy", ec.message().c_str()); + } else GenericProxyError(tr("cannot connect to upstream socks proxy"), ec.message()); } void HTTPReqHandler::HandleSocksProxySendHandshake(const boost::system::error_code & ec, std::size_t bytes_transferred) { LogPrint(eLogDebug, "HTTPProxy: upstream socks handshake sent"); - if(ec) GenericProxyError("Cannot negotiate with socks proxy", ec.message().c_str()); + if(ec) GenericProxyError(tr("Cannot negotiate with socks proxy"), ec.message()); else m_proxysock->async_read_some(boost::asio::buffer(m_socks_buf, 8), std::bind(&HTTPReqHandler::HandleSocksProxyReply, this, std::placeholders::_1, std::placeholders::_2)); } @@ -556,7 +559,7 @@ namespace proxy { } else { - GenericProxyError("CONNECT error", "Failed to Connect"); + GenericProxyError(tr("CONNECT error"), tr("Failed to Connect")); } } @@ -567,7 +570,7 @@ namespace proxy { m_send_buf = m_ClientResponse.to_string(); boost::asio::async_write(*m_sock, boost::asio::buffer(m_send_buf), boost::asio::transfer_all(), [&] (const boost::system::error_code & ec, std::size_t transferred) { - if(ec) GenericProxyError("socks proxy error", ec.message().c_str()); + if(ec) GenericProxyError(tr("socks proxy error"), ec.message()); else HandoverToUpstreamProxy(); }); } else { @@ -575,7 +578,7 @@ namespace proxy { LogPrint(eLogDebug, "HTTPProxy: send ", m_send_buf.size(), " bytes"); boost::asio::async_write(*m_proxysock, boost::asio::buffer(m_send_buf), boost::asio::transfer_all(), [&](const boost::system::error_code & ec, std::size_t transferred) { - if(ec) GenericProxyError("failed to send request to upstream", ec.message().c_str()); + if(ec) GenericProxyError(tr("failed to send request to upstream"), ec.message()); else HandoverToUpstreamProxy(); }); } @@ -593,18 +596,18 @@ namespace proxy { ss << "error code: "; ss << (int) m_socks_buf[1]; std::string msg = ss.str(); - GenericProxyError("Socks Proxy error", msg.c_str()); + GenericProxyError(tr("socks proxy error"), msg); } } - else GenericProxyError("No Reply From socks proxy", ec.message().c_str()); + else GenericProxyError(tr("No Reply From socks proxy"), ec.message()); } void HTTPReqHandler::HandleUpstreamHTTPProxyConnect(const boost::system::error_code & ec) { if(!ec) { LogPrint(eLogDebug, "HTTPProxy: connected to http upstream"); - GenericProxyError("cannot connect", "http out proxy not implemented"); - } else GenericProxyError("cannot connect to upstream http proxy", ec.message().c_str()); + GenericProxyError(tr("cannot connect"), tr("http out proxy not implemented")); + } else GenericProxyError(tr("cannot connect to upstream http proxy"), ec.message()); } /* will be called after some data received from client */ @@ -637,7 +640,7 @@ namespace proxy { { if (!stream) { LogPrint (eLogError, "HTTPProxy: error when creating the stream, check the previous warnings for more info"); - GenericProxyError("Host is down", "Can't create connection to requested host, it may be down. Please try again later."); + GenericProxyError(tr("Host is down"), tr("Can't create connection to requested host, it may be down. Please try again later.")); return; } if (Kill()) From e687773b412d62c16d332a39f703c3d0db3ef037 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 23 May 2021 10:50:26 +0300 Subject: [PATCH 4/6] [18n] translate webconsole Signed-off-by: R4SAS --- daemon/HTTPServer.cpp | 434 ++++++++++++++++++++++-------------------- i18n/russian.cpp | 160 +++++++++++++++- 2 files changed, 381 insertions(+), 213 deletions(-) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 87744f37..22cefedd 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -41,7 +41,7 @@ namespace i2p { namespace http { - const char *itoopieFavicon = + const std::string itoopieFavicon = "data:image/png;base64," "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACx" "jwv8YQUAAAAJcEhZcwAALiIAAC4iAari3ZIAAAAHdElNRQfgCQsUNSZrkhi1AAAAGXRFWHRTb2Z0" @@ -59,49 +59,51 @@ namespace http { "JHYnlIsfzJjIp9xZKswL5YKBHL+coKJoRDaUSzoozxHVrygQU4JykQADAwAT5b1NHtwZugAAAABJ" "RU5ErkJggg=="; - const char *cssStyles = - "\r\n"; + static void GetStyles (std::stringstream& s) + { + s << "\r\n"; + } const char HTTP_PAGE_TUNNELS[] = "tunnels"; const char HTTP_PAGE_TRANSIT_TUNNELS[] = "transit_tunnels"; @@ -155,28 +157,35 @@ namespace http { s << std::fixed << std::setprecision(2); auto numKBytes = (double) bytes / 1024; if (numKBytes < 1024) - s << numKBytes << " KiB"; + s << numKBytes << " " << tr("KiB"); else if (numKBytes < 1024 * 1024) - s << numKBytes / 1024 << " MiB"; + s << numKBytes / 1024 << " " << tr("MiB"); else - s << numKBytes / 1024 / 1024 << " GiB"; + s << numKBytes / 1024 / 1024 << " " << tr("GiB"); } static void ShowTunnelDetails (std::stringstream& s, enum i2p::tunnel::TunnelState eState, bool explr, int bytes) { - std::string state; + std::string state, stateText; switch (eState) { case i2p::tunnel::eTunnelStateBuildReplyReceived : - case i2p::tunnel::eTunnelStatePending : state = "building"; break; + case i2p::tunnel::eTunnelStatePending : state = "building"; break; case i2p::tunnel::eTunnelStateBuildFailed : case i2p::tunnel::eTunnelStateTestFailed : - case i2p::tunnel::eTunnelStateFailed : state = "failed"; break; - case i2p::tunnel::eTunnelStateExpiring : state = "expiring"; break; + case i2p::tunnel::eTunnelStateFailed : state = "failed"; break; + case i2p::tunnel::eTunnelStateExpiring : state = "expiring"; break; case i2p::tunnel::eTunnelStateEstablished : state = "established"; break; default: state = "unknown"; break; } - s << " " << state << ((explr) ? " (exploratory)" : "") << ", "; - s << " " << (int) (bytes / 1024) << " KiB\r\n"; + + if (state == "building") stateText = tr("building"); + else if (state == "failed") stateText = tr("failed"); + else if (state == "expiring") stateText = tr("expiring"); + else if (state == "established") stateText = tr("established"); + else stateText = tr("unknown"); + + s << " " << stateText << ((explr) ? " (" + tr("exploratory") + ")" : "") << ", "; + s << " " << (int) (bytes / 1024) << " " << tr("KiB") << "\r\n"; } static void SetLogLevel (const std::string& level) @@ -192,35 +201,40 @@ namespace http { static void ShowPageHead (std::stringstream& s) { - std::string webroot; - i2p::config::GetOption("http.webroot", webroot); + std::string webroot; i2p::config::GetOption("http.webroot", webroot); + + // Page language + std::string lang, langCode; i2p::config::GetOption("http.lang", lang); + if (lang == "russian") langCode = "ru"; + else langCode = "en"; + s << "\r\n" - "\r\n" /* TODO: Add support for locale */ + "\r\n" " \r\n" /* TODO: Find something to parse html/template system. This is horrible. */ " \r\n" " \r\n" " \r\n" - " Purple I2P " VERSION " Webconsole\r\n" - << cssStyles << - "\r\n"; + " Purple I2P " VERSION " Webconsole\r\n"; + GetStyles(s); s << + "\r\n" "\r\n" - "
    i2pd webconsole
    \r\n" + "
    " << tr("i2pd webconsole") << "
    \r\n" "
    \r\n" "
    \r\n" - " Main page
    \r\n" - " Router commands\r\n" - " Local destinations\r\n"; + " " << tr("Main page") << "
    \r\n" + " " << tr("Router commands") << "\r\n" + " " << tr("Local destinations") << "\r\n"; if (i2p::context.IsFloodfill ()) - s << " LeaseSets\r\n"; + s << " " << tr("LeaseSets") << "\r\n"; s << - " Tunnels\r\n" - " Transit tunnels\r\n" - " Transports\r\n" - " I2P tunnels\r\n"; + " " << tr("Tunnels") << "\r\n" + " " << tr("Transit tunnels") << "\r\n" + " " << tr ("Transports") << "\r\n" + " " << tr("I2P tunnels") << "\r\n"; if (i2p::client::context.GetSAMBridge ()) - s << " SAM sessions\r\n"; + s << " " << tr("SAM sessions") << "\r\n"; s << "
    \r\n" "
    "; @@ -236,94 +250,94 @@ namespace http { static void ShowError(std::stringstream& s, const std::string& string) { - s << "ERROR: " << string << "
    \r\n"; + s << "" << tr("ERROR") << ": " << string << "
    \r\n"; } static void ShowNetworkStatus (std::stringstream& s, RouterStatus status) { switch (status) { - case eRouterStatusOK: s << "OK"; break; - case eRouterStatusTesting: s << "Testing"; break; - case eRouterStatusFirewalled: s << "Firewalled"; break; - case eRouterStatusUnknown: s << "Unknown"; break; - case eRouterStatusProxy: s << "Proxy"; break; - case eRouterStatusMesh: s << "Mesh"; break; + case eRouterStatusOK: s << tr("OK"); break; + case eRouterStatusTesting: s << tr("Testing"); break; + case eRouterStatusFirewalled: s << tr("Firewalled"); break; + case eRouterStatusUnknown: s << tr("Unknown"); break; + case eRouterStatusProxy: s << tr("Proxy"); break; + case eRouterStatusMesh: s << tr("Mesh"); break; case eRouterStatusError: { - s << "Error"; + s << tr("Error"); switch (i2p::context.GetError ()) { case eRouterErrorClockSkew: - s << " - Clock skew"; + s << " - " << tr("Clock skew"); break; case eRouterErrorOffline: - s << " - Offline"; + s << " - " << tr("Offline"); break; case eRouterErrorSymmetricNAT: - s << " - Symmetric NAT"; + s << " - " << tr("Symmetric NAT"); break; default: ; } break; } - default: s << "Unknown"; + default: s << tr("Unknown"); } } void ShowStatus (std::stringstream& s, bool includeHiddenContent, i2p::http::OutputFormatEnum outputFormat) { - s << "Uptime: "; + s << "" << tr("Uptime") << ": "; ShowUptime(s, i2p::context.GetUptime ()); s << "
    \r\n"; - s << "Network status: "; + s << "" << tr("Network status") << ": "; ShowNetworkStatus (s, i2p::context.GetStatus ()); s << "
    \r\n"; if (i2p::context.SupportsV6 ()) { - s << "Network status 6: "; + s << "" << tr("Network status v6") << ": "; ShowNetworkStatus (s, i2p::context.GetStatusV6 ()); s << "
    \r\n"; } #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) if (auto remains = Daemon.gracefulShutdownInterval) { - s << "Stopping in: "; + s << "" << tr("Stopping in") << ": "; ShowUptime(s, remains); s << "
    \r\n"; } #elif defined(WIN32_APP) if (i2p::win32::g_GracefulShutdownEndtime != 0) { uint16_t remains = (i2p::win32::g_GracefulShutdownEndtime - GetTickCount()) / 1000; - s << "Stopping in: "; + s << "" << tr("Stopping in") << ": "; ShowUptime(s, remains); s << "
    \r\n"; } #endif auto family = i2p::context.GetFamily (); if (family.length () > 0) - s << "Family: " << family << "
    \r\n"; - s << "Tunnel creation success rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%
    \r\n"; - s << "Received: "; + s << ""<< tr("Family") << ": " << family << "
    \r\n"; + s << "" << tr("Tunnel creation success rate") << ": " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%
    \r\n"; + s << "" << tr("Received") << ": "; ShowTraffic (s, i2p::transport::transports.GetTotalReceivedBytes ()); - s << " (" << (double) i2p::transport::transports.GetInBandwidth () / 1024 << " KiB/s)
    \r\n"; - s << "Sent: "; + s << " (" << (double) i2p::transport::transports.GetInBandwidth () / 1024 << " " << tr("KiB/s") << ")
    \r\n"; + s << "" << tr("Sent") << ": "; ShowTraffic (s, i2p::transport::transports.GetTotalSentBytes ()); - s << " (" << (double) i2p::transport::transports.GetOutBandwidth () / 1024 << " KiB/s)
    \r\n"; - s << "Transit: "; + s << " (" << (double) i2p::transport::transports.GetOutBandwidth () / 1024 << " " << tr("KiB/s") << ")
    \r\n"; + s << "" << tr("Transit") << ": "; ShowTraffic (s, i2p::transport::transports.GetTotalTransitTransmittedBytes ()); - s << " (" << (double) i2p::transport::transports.GetTransitBandwidth () / 1024 << " KiB/s)
    \r\n"; - s << "Data path: " << i2p::fs::GetUTF8DataDir() << "
    \r\n"; + s << " (" << (double) i2p::transport::transports.GetTransitBandwidth () / 1024 << " " << tr("KiB/s") << ")
    \r\n"; + s << "" << tr("Data path") << ": " << i2p::fs::GetUTF8DataDir() << "
    \r\n"; s << "
    "; - if((outputFormat==OutputFormatEnum::forWebConsole)||!includeHiddenContent) { - s << "\r\n\r\n
    \r\n"; + if((outputFormat == OutputFormatEnum::forWebConsole) || !includeHiddenContent) { + s << "\r\n\r\n
    \r\n"; } if(includeHiddenContent) { - s << "Router Ident: " << i2p::context.GetRouterInfo().GetIdentHashBase64() << "
    \r\n"; + s << "" << tr("Router Ident") << ": " << i2p::context.GetRouterInfo().GetIdentHashBase64() << "
    \r\n"; if (!i2p::context.GetRouterInfo().GetProperty("family").empty()) - s << "Router Family: " << i2p::context.GetRouterInfo().GetProperty("family") << "
    \r\n"; - s << "Router Caps: " << i2p::context.GetRouterInfo().GetProperty("caps") << "
    \r\n"; - s << "Version: " VERSION "
    \r\n"; - s << "Our external address:" << "
    \r\n\r\n"; + s << "" << tr("Router Family") << ": " << i2p::context.GetRouterInfo().GetProperty("family") << "
    \r\n"; + s << "" << tr("Router Caps") << ": " << i2p::context.GetRouterInfo().GetProperty("caps") << "
    \r\n"; + s << "" << tr("Version") << ": " VERSION "
    \r\n"; + s << ""<< tr("Our external address") << ":" << "
    \r\n
    \r\n"; for (const auto& address : i2p::context.GetRouterInfo().GetAddresses()) { s << "\r\n"; @@ -331,7 +345,7 @@ namespace http { { s << "\r\n\r\n"; + s << "\r\n\r\n"; continue; } switch (address->transportStyle) @@ -353,32 +367,32 @@ namespace http { break; } default: - s << "\r\n"; + s << "\r\n"; } s << "\r\n\r\n"; } s << "
    NTCP2"; if (address->host.is_v6 ()) s << "v6"; - s << "supported
    " << tr("supported") << "
    Unknown" << tr("Unknown") << "" << address->host.to_string() << ":" << address->port << "
    \r\n"; } s << "
    \r\n
    \r\n"; - if(outputFormat==OutputFormatEnum::forQtUi) { + if(outputFormat == OutputFormatEnum::forQtUi) { s << "
    "; } - s << "Routers: " << i2p::data::netdb.GetNumRouters () << " "; - s << "Floodfills: " << i2p::data::netdb.GetNumFloodfills () << " "; - s << "LeaseSets: " << i2p::data::netdb.GetNumLeaseSets () << "
    \r\n"; + s << "" << tr("Routers") << ": " << i2p::data::netdb.GetNumRouters () << " "; + s << "" << tr("Floodfills") << ": " << i2p::data::netdb.GetNumFloodfills () << " "; + s << "" << tr("LeaseSets") << ": " << i2p::data::netdb.GetNumLeaseSets () << "
    \r\n"; size_t clientTunnelCount = i2p::tunnel::tunnels.CountOutboundTunnels(); clientTunnelCount += i2p::tunnel::tunnels.CountInboundTunnels(); size_t transitTunnelCount = i2p::tunnel::tunnels.CountTransitTunnels(); - s << "Client Tunnels: " << std::to_string(clientTunnelCount) << " "; - s << "Transit Tunnels: " << std::to_string(transitTunnelCount) << "
    \r\n
    \r\n"; + s << "" << tr("Client Tunnels") << ": " << std::to_string(clientTunnelCount) << " "; + s << "" << tr("Transit Tunnels") << ": " << std::to_string(transitTunnelCount) << "
    \r\n
    \r\n"; if(outputFormat==OutputFormatEnum::forWebConsole) { bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol); - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; + s << "
    Services
    " << "HTTP Proxy" << "
    " << "SOCKS Proxy" << "
    \r\n"; + s << "\r\n"; + s << "\r\n"; s << "\r\n"; s << "\r\n"; s << "\r\n"; @@ -390,7 +404,7 @@ namespace http { void ShowLocalDestinations (std::stringstream& s) { std::string webroot; i2p::config::GetOption("http.webroot", webroot); - s << "Local Destinations:
    \r\n
    \r\n"; + s << "" << tr("Local Destinations") << ":
    \r\n
    \r\n"; for (auto& it: i2p::client::context.GetDestinations ()) { auto ident = it.second->GetIdentHash (); @@ -402,7 +416,7 @@ namespace http { auto i2cpServer = i2p::client::context.GetI2CPServer (); if (i2cpServer && !(i2cpServer->GetSessions ().empty ())) { - s << "
    I2CP Local Destinations:
    \r\n
    \r\n"; + s << "
    I2CP "<< tr("Local Destinations") << ":
    \r\n
    \r\n"; for (auto& it: i2cpServer->GetSessions ()) { auto dest = it.second->GetDestination (); @@ -425,7 +439,7 @@ namespace http { if (dest->IsEncryptedLeaseSet ()) { i2p::data::BlindedPublicKey blinded (dest->GetIdentity (), dest->IsPerClientAuth ()); - s << "
    \r\n\r\n
    \r\n"; + s << "
    \r\n\r\n
    \r\n"; s << blinded.ToB33 () << ".b32.i2p
    \r\n"; s << "
    \r\n
    \r\n"; } @@ -434,67 +448,67 @@ namespace http { { std::string webroot; i2p::config::GetOption("http.webroot", webroot); auto base32 = dest->GetIdentHash ().ToBase32 (); - s << "
    \r\n\r\n
    \r\n" + s << "
    \r\n\r\n
    \r\n" "
    \r\n" " \r\n" " \r\n" " \r\n" - " Domain:\r\n\r\n" - " \r\n" + " " << tr("Domain") << ":\r\n\r\n" + " \r\n" "\r\nNote: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.\r\n
    \r\n
    \r\n
    \r\n"; } if(dest->GetNumRemoteLeaseSets()) { - s << "
    \r\n\r\n
    \r\n
    " << tr("Services") << "
    " << "HTTP " << tr("Proxy") << "
    " << "SOCKS " << tr("Proxy") << "
    " << "BOB" << "
    " << "SAM" << "
    " << "I2CP" << "
    "; + s << "
    \r\n\r\n
    \r\n
    AddressTypeEncType
    "; for(auto& it: dest->GetLeaseSets ()) s << "\r\n"; s << "
    "<< tr("Address") << "" << tr("Type") << "" << tr("EncType") << "
    " << it.first.ToBase32 () << "" << (int)it.second->GetStoreType () << "" << (int)it.second->GetEncryptionType () <<"
    \r\n
    \r\n
    \r\n
    \r\n"; } else - s << "LeaseSets: 0
    \r\n
    \r\n"; + s << "" << tr("LeaseSets") << ": 0
    \r\n
    \r\n"; auto pool = dest->GetTunnelPool (); if (pool) { - s << "Inbound tunnels:
    \r\n
    \r\n"; + s << "" << tr("Inbound tunnels") << ":
    \r\n
    \r\n"; for (auto & it : pool->GetInboundTunnels ()) { s << "
    "; it->Print(s); if(it->LatencyIsKnown()) - s << " ( " << it->GetMeanLatency() << "ms )"; + s << " ( " << it->GetMeanLatency() << tr("ms") << " )"; ShowTunnelDetails(s, it->GetState (), false, it->GetNumReceivedBytes ()); s << "
    \r\n"; } s << "
    \r\n"; - s << "Outbound tunnels:
    \r\n
    \r\n"; + s << "" << tr("Outbound tunnels") << ":
    \r\n
    \r\n"; for (auto & it : pool->GetOutboundTunnels ()) { s << "
    "; it->Print(s); if(it->LatencyIsKnown()) - s << " ( " << it->GetMeanLatency() << "ms )"; + s << " ( " << it->GetMeanLatency() << tr("ms") << " )"; ShowTunnelDetails(s, it->GetState (), false, it->GetNumSentBytes ()); s << "
    \r\n"; } } s << "
    \r\n"; - s << "Tags
    \r\nIncoming: " << dest->GetNumIncomingTags () << "
    \r\n"; + s << "" << tr("Tags") << "
    \r\n" << tr("Incoming") << ": " << dest->GetNumIncomingTags () << "
    \r\n"; if (!dest->GetSessions ().empty ()) { std::stringstream tmp_s; uint32_t out_tags = 0; for (const auto& it: dest->GetSessions ()) { tmp_s << "" << i2p::client::context.GetAddressBook ().ToAddress(it.first) << "" << it.second->GetNumOutgoingTags () << "\r\n"; out_tags += it.second->GetNumOutgoingTags (); } - s << "
    \r\n\r\n" - << "
    \r\n\r\n\r\n\r\n" << tmp_s.str () << "
    DestinationAmount
    \r\n
    \r\n
    \r\n"; + s << "
    \r\n\r\n" + << "
    \r\n\r\n\r\n\r\n" << tmp_s.str () << "
    " << tr("Destination") << "" << tr("Amount") << "
    \r\n
    \r\n
    \r\n"; } else - s << "Outgoing: 0
    \r\n"; + s << tr("Outgoing") << ": 0
    \r\n"; s << "
    \r\n"; auto numECIESx25519Tags = dest->GetNumIncomingECIESx25519Tags (); if (numECIESx25519Tags > 0) { - s << "ECIESx25519
    \r\nIncoming Tags: " << numECIESx25519Tags << "
    \r\n"; + s << "ECIESx25519
    \r\n" << tr("Incoming Tags") << ": " << numECIESx25519Tags << "
    \r\n"; if (!dest->GetECIESx25519Sessions ().empty ()) { std::stringstream tmp_s; uint32_t ecies_sessions = 0; @@ -502,17 +516,17 @@ namespace http { tmp_s << "" << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetDestination ()) << "" << it.second->GetState () << "\r\n"; ecies_sessions++; } - s << "
    \r\n\r\n" - << "
    \r\n\r\n\r\n\r\n" << tmp_s.str () << "
    DestinationStatus
    \r\n
    \r\n
    \r\n"; + s << "
    \r\n\r\n" + << "
    \r\n\r\n\r\n\r\n" << tmp_s.str () << "
    " << tr("Destination") << "" << tr("Status") << "
    \r\n
    \r\n
    \r\n"; } else - s << "Tags sessions: 0
    \r\n"; + s << tr("Tags sessions") << ": 0
    \r\n"; s << "
    \r\n"; } } void ShowLocalDestination (std::stringstream& s, const std::string& b32, uint32_t token) { - s << "Local Destination:
    \r\n
    \r\n"; + s << "" << tr("Local Destination") << ":
    \r\n
    \r\n"; i2p::data::IdentHash ident; ident.FromBase32 (b32); auto dest = i2p::client::context.FindLocalDestination (ident); @@ -521,7 +535,7 @@ namespace http { { ShowLeaseSetDestination (s, dest, token); // show streams - s << "\r\n\r\n\r\n"; + s << "
    Streams
    \r\n\r\n\r\n"; s << ""; s << ""; @@ -543,7 +557,7 @@ namespace http { s << ""; if (it->GetRecvStreamID ()) { s << ""; + << it->GetRecvStreamID () << "&token=" << token << "\" title=\"" << tr("Close stream") << "\"> ✘ "; } else { s << "
    " << tr("Streams") << "
    StreamID"; // Stream closing button column s << "Destination" << it->GetRecvStreamID () << ""; } @@ -567,22 +581,22 @@ namespace http { auto i2cpServer = i2p::client::context.GetI2CPServer (); if (i2cpServer) { - s << "I2CP Local Destination:
    \r\n
    \r\n"; + s << "I2CP " << tr("Local Destination") << ":
    \r\n
    \r\n"; auto it = i2cpServer->GetSessions ().find (std::stoi (id)); if (it != i2cpServer->GetSessions ().end ()) ShowLeaseSetDestination (s, it->second->GetDestination (), 0); else - ShowError(s, "I2CP session not found"); + ShowError(s, tr("I2CP session not found")); } else - ShowError(s, "I2CP is not enabled"); + ShowError(s, tr("I2CP is not enabled")); } void ShowLeasesSets(std::stringstream& s) { if (i2p::data::netdb.GetNumLeaseSets ()) { - s << "LeaseSets:
    \r\n
    \r\n"; + s << "" << tr("LeaseSets") << ":
    \r\n
    \r\n"; int counter = 1; // for each lease set i2p::data::netdb.VisitLeaseSets( @@ -601,21 +615,21 @@ namespace http { s << " expired"; // additional css class for expired s << "\">\r\n"; if (!ls->IsValid()) - s << "
    !! Invalid !!
    \r\n"; + s << "
    !! " << tr("Invalid") << " !!
    \r\n"; s << "
    \r\n"; s << "\r\n
    \r\n"; - s << "Store type: " << (int)storeType << "
    \r\n"; - s << "Expires: " << ConvertTime(ls->GetExpirationTime()) << "
    \r\n"; + s << "" << tr("Store type") << ": " << (int)storeType << "
    \r\n"; + s << "" << tr("Expires") << ": " << ConvertTime(ls->GetExpirationTime()) << "
    \r\n"; if (storeType == i2p::data::NETDB_STORE_TYPE_LEASESET || storeType == i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2) { // leases information is available auto leases = ls->GetNonExpiredLeases(); - s << "Non Expired Leases: " << leases.size() << "
    \r\n"; + s << "" << tr("Non Expired Leases") << ": " << leases.size() << "
    \r\n"; for ( auto & l : leases ) { - s << "Gateway: " << l->tunnelGateway.ToBase64() << "
    \r\n"; - s << "TunnelID: " << l->tunnelID << "
    \r\n"; - s << "EndDate: " << ConvertTime(l->endDate) << "
    \r\n"; + s << "" << tr("Gateway") << ": " << l->tunnelGateway.ToBase64() << "
    \r\n"; + s << "" << tr("TunnelID") << ": " << l->tunnelID << "
    \r\n"; + s << "" << tr("EndDate") << ": " << ConvertTime(l->endDate) << "
    \r\n"; } } s << "
    \r\n
    \r\n
    \r\n"; @@ -625,37 +639,37 @@ namespace http { } else if (!i2p::context.IsFloodfill ()) { - s << "LeaseSets: not floodfill.
    \r\n"; + s << "" << tr("LeaseSets") << ": " << tr("not floodfill") << ".
    \r\n"; } else { - s << "LeaseSets: 0
    \r\n"; + s << "" << tr("LeaseSets") << ": 0
    \r\n"; } } void ShowTunnels (std::stringstream& s) { - s << "Tunnels:
    \r\n"; - s << "Queue size: " << i2p::tunnel::tunnels.GetQueueSize () << "
    \r\n
    \r\n"; + s << "" << tr("Tunnels") << ":
    \r\n"; + s << "" << tr("Queue size") << ": " << i2p::tunnel::tunnels.GetQueueSize () << "
    \r\n
    \r\n"; auto ExplPool = i2p::tunnel::tunnels.GetExploratoryPool (); - s << "Inbound tunnels:
    \r\n
    \r\n"; + s << "" << tr("Inbound tunnels") << ":
    \r\n
    \r\n"; for (auto & it : i2p::tunnel::tunnels.GetInboundTunnels ()) { s << "
    "; it->Print(s); if(it->LatencyIsKnown()) - s << " ( " << it->GetMeanLatency() << "ms )"; + s << " ( " << it->GetMeanLatency() << tr("ms") << " )"; ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumReceivedBytes ()); s << "
    \r\n"; } s << "
    \r\n
    \r\n"; - s << "Outbound tunnels:
    \r\n
    \r\n"; + s << "" << tr("Outbound tunnels") << ":
    \r\n
    \r\n"; for (auto & it : i2p::tunnel::tunnels.GetOutboundTunnels ()) { s << "
    "; it->Print(s); if(it->LatencyIsKnown()) - s << " ( " << it->GetMeanLatency() << "ms )"; + s << " ( " << it->GetMeanLatency() << tr("ms") << " )"; ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumSentBytes ()); s << "
    \r\n"; } @@ -666,30 +680,30 @@ namespace http { { std::string webroot; i2p::config::GetOption("http.webroot", webroot); /* commands */ - s << "Router Commands
    \r\n
    \r\n
    \r\n"; - s << " Run peer test\r\n"; + s << "" << tr("Router commands") << "
    \r\n
    \r\n
    \r\n"; + s << " " << tr("Run peer test") << "\r\n"; //s << " Reload config
    \r\n"; if (i2p::context.AcceptsTunnels ()) - s << " Decline transit tunnels\r\n"; + s << " " << tr("Decline transit tunnels") << "\r\n"; else - s << " Accept transit tunnels\r\n"; + s << " " << tr("Accept transit tunnels") << "\r\n"; #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) if (Daemon.gracefulShutdownInterval) - s << " Cancel graceful shutdown\r\n"; + s << " " << tr("Cancel graceful shutdown") << "\r\n"; else - s << " Start graceful shutdown
    \r\n"; + s << " " << tr("Start graceful shutdown") << "
    \r\n"; #elif defined(WIN32_APP) if (i2p::util::DaemonWin32::Instance().isGraceful) - s << " Cancel graceful shutdown\r\n"; + s << " " << tr("Cancel graceful shutdown") << "\r\n"; else - s << " Graceful shutdown\r\n"; + s << " " << tr("Start graceful shutdown") << "\r\n"; #endif - s << " Force shutdown\r\n"; + s << " " << tr("Force shutdown") << "\r\n"; s << "
    "; - s << "
    \r\nNote: any action done here are not persistent and not changes your config files.\r\n
    \r\n"; + s << "
    \r\n" << tr("Note: any action done here are not persistent and not changes your config files.") << "\r\n
    \r\n"; - s << "Logging level
    \r\n"; + s << "" << tr("Logging level") << "
    \r\n"; s << " none \r\n"; s << " error \r\n"; s << " warn \r\n"; @@ -697,12 +711,12 @@ namespace http { s << " debug
    \r\n
    \r\n"; uint16_t maxTunnels = GetMaxNumTransitTunnels (); - s << "Transit tunnels limit
    \r\n"; + s << "" << tr("Transit tunnels limit") << "
    \r\n"; s << "
    \r\n"; s << " \r\n"; s << " \r\n"; s << " \r\n"; - s << " \r\n"; + s << " \r\n"; s << "
    \r\n
    \r\n"; } @@ -710,7 +724,7 @@ namespace http { { if(i2p::tunnel::tunnels.CountTransitTunnels()) { - s << "Transit tunnels:
    \r\n
    \r\n"; + s << "" << tr("Transit tunnels") << ":
    \r\n
    \r\n"; for (const auto& it: i2p::tunnel::tunnels.GetTransitTunnels ()) { s << "
    \r\n"; @@ -726,7 +740,7 @@ namespace http { } else { - s << "Transit tunnels: no transit tunnels currently built.
    \r\n"; + s << "" << tr("Transit tunnels") << ": " << tr("no transit tunnels currently built") << ".
    \r\n"; } } @@ -775,7 +789,7 @@ namespace http { void ShowTransports (std::stringstream& s) { - s << "Transports:
    \r\n"; + s << "" << tr("Transports") << ":
    \r\n"; auto ntcp2Server = i2p::transport::transports.GetNTCP2Server (); if (ntcp2Server) { @@ -831,13 +845,13 @@ namespace http { auto sam = i2p::client::context.GetSAMBridge (); if (!sam) { - ShowError(s, "SAM disabled"); + ShowError(s, tr("SAM disabled")); return; } if(sam->GetSessions ().size ()) { - s << "SAM Sessions:
    \r\n
    \r\n"; + s << "" << tr("SAM sessions") << ":
    \r\n
    \r\n"; for (auto& it: sam->GetSessions ()) { auto& name = it.second->GetLocalDestination ()->GetNickname (); @@ -847,30 +861,30 @@ namespace http { s << "
    \r\n"; } else - s << "SAM Sessions: no sessions currently running.
    \r\n"; + s << "" << tr("SAM sessions") << ": " << tr("no sessions currently running") << ".
    \r\n"; } void ShowSAMSession (std::stringstream& s, const std::string& id) { auto sam = i2p::client::context.GetSAMBridge (); if (!sam) { - ShowError(s, "SAM disabled"); + ShowError(s, tr("SAM disabled")); return; } auto session = sam->FindSession (id); if (!session) { - ShowError(s, "SAM session not found"); + ShowError(s, tr("SAM session not found")); return; } std::string webroot; i2p::config::GetOption("http.webroot", webroot); - s << "SAM Session:
    \r\n
    \r\n"; + s << "" << tr("SAM Session") << ":
    \r\n
    \r\n"; auto& ident = session->GetLocalDestination ()->GetIdentHash(); s << "\r\n"; s << "
    \r\n"; - s << "Streams:
    \r\n
    \r\n"; + s << "" << tr("Streams") << ":
    \r\n
    \r\n"; for (const auto& it: sam->ListSockets(id)) { s << "
    "; @@ -879,7 +893,7 @@ namespace http { case i2p::client::eSAMSocketTypeSession : s << "session"; break; case i2p::client::eSAMSocketTypeStream : s << "stream"; break; case i2p::client::eSAMSocketTypeAcceptor : s << "acceptor"; break; - case i2p::client::eSAMSocketTypeForward : s << "forward"; break; + case i2p::client::eSAMSocketTypeForward : s << "forward"; break; default: s << "unknown"; break; } s << " [" << it->GetSocket ().remote_endpoint() << "]"; @@ -891,7 +905,7 @@ namespace http { void ShowI2PTunnels (std::stringstream& s) { std::string webroot; i2p::config::GetOption("http.webroot", webroot); - s << "Client Tunnels:
    \r\n
    \r\n"; + s << "" << tr("Client Tunnels") << ":
    \r\n
    \r\n"; for (auto& it: i2p::client::context.GetClientTunnels ()) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); @@ -905,7 +919,7 @@ namespace http { { auto& ident = httpProxy->GetLocalDestination ()->GetIdentHash(); s << "
    "; - s << "HTTP Proxy" << " ⇐ "; + s << "HTTP " << tr("Proxy") << " ⇐ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << "
    \r\n"<< std::endl; } @@ -914,7 +928,7 @@ namespace http { { auto& ident = socksProxy->GetLocalDestination ()->GetIdentHash(); s << "
    "; - s << "SOCKS Proxy" << " ⇐ "; + s << "SOCKS " << tr("Proxy") << " ⇐ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << "
    \r\n"<< std::endl; } @@ -922,7 +936,7 @@ namespace http { auto& serverTunnels = i2p::client::context.GetServerTunnels (); if (!serverTunnels.empty ()) { - s << "
    \r\nServer Tunnels:
    \r\n
    \r\n"; + s << "
    \r\n" << tr("Server Tunnels") << ":
    \r\n
    \r\n"; for (auto& it: serverTunnels) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); @@ -938,7 +952,7 @@ namespace http { auto& clientForwards = i2p::client::context.GetClientForwards (); if (!clientForwards.empty ()) { - s << "
    \r\nClient Forwards:
    \r\n
    \r\n"; + s << "
    \r\n" << tr("Client Forwards") << ":
    \r\n
    \r\n"; for (auto& it: clientForwards) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); @@ -952,7 +966,7 @@ namespace http { auto& serverForwards = i2p::client::context.GetServerForwards (); if (!serverForwards.empty ()) { - s << "
    \r\nServer Forwards:
    \r\n
    \r\n"; + s << "
    \r\n" << tr("Server Forwards") << ":
    \r\n
    \r\n"; for (auto& it: serverForwards) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); @@ -1084,7 +1098,7 @@ namespace http { return; } } - // Html5 head start + // HTML head start ShowPageHead (s); if (req.uri.find("page=") != std::string::npos) { HandlePage (req, res, s); @@ -1158,7 +1172,7 @@ namespace http { ShowLeasesSets(s); else { res.code = 400; - ShowError(s, "Unknown page: " + page); + ShowError(s, tr("Unknown page") + ": " + page); return; } } @@ -1177,7 +1191,7 @@ namespace http { if (token.empty () || m_Tokens.find (std::stoi (token)) == m_Tokens.end ()) { - ShowError(s, "Invalid token"); + ShowError(s, tr("Invalid token")); return; } @@ -1235,18 +1249,18 @@ namespace http { if (dest) { if(dest->DeleteStream (streamID)) - s << "SUCCESS: Stream closed
    \r\n
    \r\n"; + s << "" << tr("SUCCESS") << ": " << tr("Stream closed") << "
    \r\n
    \r\n"; else - s << "ERROR: Stream not found or already was closed
    \r\n
    \r\n"; + s << "" << tr("ERROR") << ": " << tr("Stream not found or already was closed") << "
    \r\n
    \r\n"; } else - s << "ERROR: Destination not found
    \r\n
    \r\n"; + s << "" << tr("ERROR") << ": " << tr("Destination not found") << "
    \r\n
    \r\n"; } else - s << "ERROR: StreamID can be null
    \r\n
    \r\n"; + s << "" << tr("ERROR") << ": " << tr("StreamID can't be null") << "
    \r\n
    \r\n"; - s << "Return to destination page
    \r\n"; - s << "

    You will be redirected back in 5 seconds"; + s << "" << tr("Return to destination page") << "
    \r\n"; + s << "

    " << tr("You will be redirected back in 5 seconds") << ""; redirect = "5; url=" + webroot + "?page=local_destination&b32=" + b32; res.add_header("Refresh", redirect.c_str()); return; @@ -1257,9 +1271,9 @@ namespace http { if (limit > 0 && limit <= 65535) SetMaxNumTransitTunnels (limit); else { - s << "ERROR: Transit tunnels count must not exceed 65535\r\n
    \r\n
    \r\n"; - s << "Back to commands list\r\n
    \r\n"; - s << "

    You will be redirected back in 5 seconds"; + s << "" << tr("ERROR") << ": " << tr("Transit tunnels count must not exceed 65535") << "\r\n
    \r\n
    \r\n"; + s << "" << tr("Back to commands list") << "\r\n
    \r\n"; + s << "

    " << tr("You will be redirected back in 5 seconds") << ""; res.add_header("Refresh", redirect.c_str()); return; } @@ -1292,37 +1306,37 @@ namespace http { auto len = i2p::data::ByteStreamToBase64 (signature, signatureLen, sig, signatureLen*2); sig[len] = 0; out << "#!sig=" << sig; - s << "SUCCESS:
    \r\n

    \r\n" + s << "" << tr("SUCCESS") << ":
    \r\n\r\n" "\r\n
    \r\n
    \r\n" - "Register at reg.i2p:\r\n
    \r\n" - "Description:\r\n\r\n" - "\r\n" + "" << tr("Register at reg.i2p") << ":\r\n
    \r\n" + "" << tr("Description") << ":\r\n\r\n" + "\r\n" "
    \r\n
    \r\n"; delete[] signature; delete[] sig; } else - s << "ERROR: Domain can't end with .b32.i2p\r\n
    \r\n
    \r\n"; + s << "" << tr("ERROR") << ": " << tr("Domain can't end with .b32.i2p") << "\r\n
    \r\n
    \r\n"; } else - s << "ERROR: Domain must end with .i2p\r\n
    \r\n
    \r\n"; + s << "" << tr("ERROR") << ": " << tr("Domain must end with .i2p") << "\r\n
    \r\n
    \r\n"; } else - s << "ERROR: Such destination is not found\r\n
    \r\n
    \r\n"; + s << "" << tr("ERROR") << ": " << tr("Such destination is not found") << "\r\n
    \r\n
    \r\n"; - s << "Return to destination page\r\n"; + s << "" << tr("Return to destination page") << "\r\n"; return; } else { res.code = 400; - ShowError(s, "Unknown command: " + cmd); + ShowError(s, tr("Unknown command") + ": " + cmd); return; } - s << "SUCCESS: Command accepted

    \r\n"; - s << "Back to commands list
    \r\n"; - s << "

    You will be redirected in 5 seconds"; + s << "" << tr("SUCCESS") << ": " << tr("Command accepted") << "

    \r\n"; + s << "" << tr("Back to commands list") << "
    \r\n"; + s << "

    " << tr("You will be redirected in 5 seconds") << ""; res.add_header("Refresh", redirect.c_str()); } diff --git a/i18n/russian.cpp b/i18n/russian.cpp index feb6fc63..71f27613 100644 --- a/i18n/russian.cpp +++ b/i18n/russian.cpp @@ -52,16 +52,170 @@ namespace russian { // language {"http out proxy not implemented", "поддержка внешнего HTTP прокси сервера не реализована"}, {"cannot connect to upstream http proxy", "не удалось подключиться к вышестоящему HTTP прокси серверу"}, {"Host is down", "Адрес недоступен"}, - {"Can't create connection to requested host, it may be down. Please try again later.", "Не удалось установить соединение к запрошенному адресу, возможно он не в сети. Попробуйте повторить запрос позже."}, + {"Can't create connection to requested host, it may be down. Please try again later.", + "Не удалось установить соединение к запрошенному адресу, возможно он не в сети. Попробуйте повторить запрос позже."}, + + // Webconsole // + // cssStyles + {"Disabled", "Выключено"}, + {"Enabled", "Включено"}, + // ShowTraffic + {"KiB", "КиБ"}, + {"MiB", "МиБ"}, + {"GiB", "ГиБ"}, + // ShowTunnelDetails + {"building", "строится"}, + {"failed", "неудачный"}, + {"expiring", "заканчивается"}, + {"established", "работает"}, + {"exploratory", "исследовательский"}, + {"unknown", "неизвестно"}, + {"i2pd webconsole", "Веб-консоль i2pd"}, + // ShowPageHead + {"Main page", "Главная"}, + {"Router commands", "Команды роутера"}, + {"Local destinations", "Локальные назнач."}, + {"LeaseSets", "Лизсеты"}, + {"Tunnels", "Туннели"}, + {"Transit tunnels", "Транзит. туннели"}, + {"Transports", "Транспорты"}, + {"I2P tunnels", "I2P туннели"}, + {"SAM sessions", "SAM сессии"}, + // Network Status + {"OK", "OK"}, + {"Testing", "Тестирование"}, + {"Firewalled", "Файрвол"}, + {"Unknown", "Неизвестно"}, + {"Proxy", "Прокси"}, + {"Mesh", "MESH-сеть"}, + {"Error", "Ошибка"}, + {"Clock skew", "Не точное время"}, + {"Offline", "Оффлайн"}, + {"Symmetric NAT", "Симметричный NAT"}, + // Status + {"Uptime", "В сети"}, + {"Network status", "Сетевой статус"}, + {"Network status v6", "Сетевой статус v6"}, + {"Stopping in", "Остановка через"}, + {"Family", "Семейство"}, + {"Tunnel creation success rate", "Успешно построенных туннелей"}, + {"Received", "Получено"}, + {"Sent", "Отправлено"}, + {"Transit", "Транзит"}, + {"KiB/s", "КиБ/с"}, + {"Data path", "Путь к данным"}, + {"Hidden content. Press on text to see.", "Скрытый контент. Нажмите на текст чтобы отобразить."}, + {"Router Ident", "Идентификатор роутера"}, + {"Router Family", "Семейство роутера"}, + {"Router Caps", "Флаги роутера"}, + {"Version", "Версия"}, + {"Our external address", "Наш внешний адрес"}, + {"supported", "поддерживается"}, + {"Routers", "Роутеры"}, + {"Floodfills", "Флудфилы"}, + {"LeaseSets", "Лизсеты"}, + {"Client Tunnels", "Клиентские туннели"}, + {"Transit Tunnels", "Транзитные туннели"}, + {"Services", "Сервисы"}, + // ShowLocalDestinations + {"Local Destinations", "Локальные назначения"}, + // ShowLeaseSetDestination + {"Encrypted B33 address", "Шифрованные B33 адреса"}, + {"Address registration line", "Строка регистрации адреса"}, + {"Domain", "Домен"}, + {"Generate", "Сгенерировать"}, + {"Address", "Адрес"}, + {"Type", "Тип"}, + {"EncType", "ТипШифр"}, + {"Inbound tunnels", "Входящие туннели"}, + {"Outbound tunnels", "Исходящие туннели"}, + {"ms", "мс"}, // milliseconds + {"Tags", "Теги"}, + {"Incoming", "Входящие"}, + {"Outgoing", "Исходящие"}, + {"Destination", "Назначение"}, + {"Amount", "Количество"}, + {"Incoming Tags", "Входящие Теги"}, + {"Tags sessions", "Сессии Тегов"}, + {"Status", "Статус"}, + // ShowLocalDestination + {"Local Destination", "Локальное назначение"}, + {"Streams", "Стримы"}, + {"Close stream", "Закрыть стрим"}, + // ShowI2CPLocalDestination + {"I2CP session not found", "I2CP сессия не найдена"}, + {"I2CP is not enabled", "I2CP не включен"}, + // ShowLeasesSets + {"Invalid", "Некорректный"}, + {"Store type", "Тип хранилища"}, + {"Expires", "Истекает"}, + {"Non Expired Leases", "Не истекшие Lease-ы"}, + {"Gateway", "Шлюз"}, + {"TunnelID", "ID туннеля"}, + {"EndDate", "Заканчивается"}, + {"not floodfill", "не флудфил"}, + // ShowTunnels + {"Queue size", "Размер очереди"}, + // ShowCommands + {"Run peer test", "Запустить тестирование"}, + {"Decline transit tunnels", "Отклонять транзитные туннели"}, + {"Accept transit tunnels", "Принимать транзитные туннели"}, + {"Cancel graceful shutdown", "Отменить плавную остановку"}, + {"Start graceful shutdown", "Запустить плавную остановку"}, + {"Force shutdown", "Принудительная остановка"}, + {"Note: any action done here are not persistent and not changes your config files.", + "Примечание: любое действие произведенное здесь не является постоянным и не изменяет ваши конфигурационные файлы."}, + {"Logging level", "Уровень логирования"}, + {"Transit tunnels limit", "Лимит транзитных туннелей"}, + {"Change", "Изменить"}, + // ShowTransitTunnels + {"no transit tunnels currently built", "нет построенных транзитных туннелей"}, + // ShowSAMSessions/ShowSAMSession + {"SAM disabled", "SAM выключен"}, + {"SAM session not found", "SAM сессия не найдена"}, + {"no sessions currently running", "нет запущенных сессий"}, + {"SAM Session", "SAM сессия"}, + // ShowI2PTunnels + {"Server Tunnels", "Серверные туннели"}, + {"Client Forwards", "Клиентские переадресации"}, + {"Server Forwards", "Серверные переадресации"}, + // HandlePage + {"Unknown page", "Неизвестная страница"}, + // HandleCommand, ShowError + {"Invalid token", "Неверный токен"}, + {"SUCCESS", "УСПЕШНО"}, + {"ERROR", "ОШИБКА"}, + {"Unknown command", "Неизвестная команда"}, + {"Command accepted", "Команда принята"}, + {"Back to commands list", "Вернуться к списку команд"}, + {"You will be redirected in 5 seconds", "Вы будете переадресованы через 5 секунд"}, + // HTTP_COMMAND_KILLSTREAM + {"Stream closed", "Стрим закрыт"}, + {"Stream not found or already was closed", "Стрим не найден или уже закрыт"}, + {"Destination not found", "Точка назначения не найдена"}, + {"StreamID can't be null", "StreamID не может быть пустым"}, + {"Return to destination page", "Вернуться на страницу точки назначения"}, + {"You will be redirected back in 5 seconds", "Вы будете переадресованы назад через 5 секунд"}, + // HTTP_COMMAND_LIMITTRANSIT + {"Transit tunnels count must not exceed 65535", "Число транзитных туннелей не должно превышать 65535"}, + // HTTP_COMMAND_GET_REG_STRING + {"Register at reg.i2p", "Зарегистрировать на reg.i2p"}, + {"Description", "Описание"}, + {"A bit information about service on domain", "Немного информации о сервисе на домене"}, + {"Submit", "Отправить"}, + {"Domain can't end with .b32.i2p", "Домен не может заканчиваться на .b32.i2p"}, + {"Domain must end with .i2p", "Домен должен заканчиваться на .i2p"}, + {"Such destination is not found", "Такая точка назначения не найдена"}, {"", ""}, }; static std::map> plurals { + // ShowUptime {"days", {"день", "дня", "дней"}}, {"hours", {"час", "часа", "часов"}}, - {"minutes", {"минута", "минуты", "минут"}}, - {"seconds", {"секунда", "секунды", "секунд"}}, + {"minutes", {"минуту", "минуты", "минут"}}, + {"seconds", {"секунду", "секунды", "секунд"}}, {"", {"", ""}}, }; From a4b84517dc66267354a962ca7f1487b8ded14cf2 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 23 May 2021 10:56:20 +0300 Subject: [PATCH 5/6] [i18n] rename Russian translation, fix typo Signed-off-by: R4SAS --- i18n/{russian.cpp => Russian.cpp} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename i18n/{russian.cpp => Russian.cpp} (99%) diff --git a/i18n/russian.cpp b/i18n/Russian.cpp similarity index 99% rename from i18n/russian.cpp rename to i18n/Russian.cpp index 71f27613..d208e18f 100644 --- a/i18n/russian.cpp +++ b/i18n/Russian.cpp @@ -66,7 +66,7 @@ namespace russian { // language // ShowTunnelDetails {"building", "строится"}, {"failed", "неудачный"}, - {"expiring", "заканчивается"}, + {"expiring", "истекает"}, {"established", "работает"}, {"exploratory", "исследовательский"}, {"unknown", "неизвестно"}, From 2db035d23cff8ca17b6ec78d4e8e9a3295968fa7 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 23 May 2021 13:16:52 +0300 Subject: [PATCH 6/6] [i18n] fix addresshelper Signed-off-by: R4SAS --- libi2pd_client/HTTPProxy.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libi2pd_client/HTTPProxy.cpp b/libi2pd_client/HTTPProxy.cpp index aff165b0..d8b84e82 100644 --- a/libi2pd_client/HTTPProxy.cpp +++ b/libi2pd_client/HTTPProxy.cpp @@ -217,7 +217,8 @@ namespace proxy { b64 = i2p::http::UrlDecode(value); // if we need update exists, request formed with update param if (params["update"] == "true") { len += std::strlen("&update=true"); confirm = true; } - url.query.replace(pos - 1, len + 1, ""); // +-1 for taking ? and & before parameter + if (pos != 0 && url.query[pos-1] == '&') { pos--; len++; } // if helper is not only one query option + url.query.replace(pos, len, ""); return true; }