mirror of
https://github.com/PurpleI2P/i2pd.git
synced 2025-01-22 13:27:17 +01:00
[i18n] start multilang support for webconsole
Signed-off-by: R4SAS <r4sas@i2pmail.org>
This commit is contained in:
parent
59b471b9a2
commit
0e68fe4a57
9
Makefile
9
Makefile
|
@ -7,6 +7,7 @@ I2PD := i2pd
|
||||||
|
|
||||||
LIB_SRC_DIR := libi2pd
|
LIB_SRC_DIR := libi2pd
|
||||||
LIB_CLIENT_SRC_DIR := libi2pd_client
|
LIB_CLIENT_SRC_DIR := libi2pd_client
|
||||||
|
LANG_SRC_DIR := i18n
|
||||||
DAEMON_SRC_DIR := daemon
|
DAEMON_SRC_DIR := daemon
|
||||||
|
|
||||||
# import source files lists
|
# import source files lists
|
||||||
|
@ -49,12 +50,13 @@ ifeq ($(USE_MESHNET),yes)
|
||||||
NEEDED_CXXFLAGS += -DMESHNET
|
NEEDED_CXXFLAGS += -DMESHNET
|
||||||
endif
|
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_OBJS += $(patsubst %.cpp,obj/%.o,$(LIB_SRC))
|
||||||
LIB_CLIENT_OBJS += $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_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))
|
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)
|
all: mk_obj_dir $(ARLIB) $(ARLIB_CLIENT) $(I2PD)
|
||||||
|
|
||||||
|
@ -63,6 +65,7 @@ mk_obj_dir:
|
||||||
@mkdir -p obj/Win32
|
@mkdir -p obj/Win32
|
||||||
@mkdir -p obj/$(LIB_SRC_DIR)
|
@mkdir -p obj/$(LIB_SRC_DIR)
|
||||||
@mkdir -p obj/$(LIB_CLIENT_SRC_DIR)
|
@mkdir -p obj/$(LIB_CLIENT_SRC_DIR)
|
||||||
|
@mkdir -p obj/$(LANG_SRC_DIR)
|
||||||
@mkdir -p obj/$(DAEMON_SRC_DIR)
|
@mkdir -p obj/$(DAEMON_SRC_DIR)
|
||||||
|
|
||||||
api: mk_obj_dir $(SHLIB) $(ARLIB)
|
api: mk_obj_dir $(SHLIB) $(ARLIB)
|
||||||
|
@ -82,7 +85,7 @@ obj/%.o: %.cpp
|
||||||
# '-' is 'ignore if missing' on first run
|
# '-' is 'ignore if missing' on first run
|
||||||
-include $(DEPS)
|
-include $(DEPS)
|
||||||
|
|
||||||
$(I2PD): $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT)
|
$(I2PD): $(LANG_OBJS) $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT)
|
||||||
$(CXX) -o $@ $(LDFLAGS) $^ $(LDLIBS)
|
$(CXX) -o $@ $(LDFLAGS) $^ $(LDLIBS)
|
||||||
|
|
||||||
$(SHLIB): $(LIB_OBJS)
|
$(SHLIB): $(LIB_OBJS)
|
||||||
|
|
|
@ -30,6 +30,12 @@
|
||||||
#include "Daemon.h"
|
#include "Daemon.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "ECIESX25519AEADRatchetSession.h"
|
#include "ECIESX25519AEADRatchetSession.h"
|
||||||
|
#include "I18N.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
#include <codecvt>
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef WIN32_APP
|
#ifdef WIN32_APP
|
||||||
#include "Win32App.h"
|
#include "Win32App.h"
|
||||||
|
@ -130,23 +136,37 @@ namespace http {
|
||||||
static std::string ConvertTime (uint64_t time);
|
static std::string ConvertTime (uint64_t time);
|
||||||
std::map<uint32_t, uint32_t> HTTPConnection::m_Tokens;
|
std::map<uint32_t, uint32_t> 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<wchar_t>() ) );
|
||||||
|
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)
|
static void ShowUptime (std::stringstream& s, int seconds)
|
||||||
{
|
{
|
||||||
int num;
|
int num;
|
||||||
|
|
||||||
if ((num = seconds / 86400) > 0) {
|
if ((num = seconds / 86400) > 0) {
|
||||||
s << num << " days, ";
|
s << num << " " << tr("days", num) << ", ";
|
||||||
seconds -= num * 86400;
|
seconds -= num * 86400;
|
||||||
}
|
}
|
||||||
if ((num = seconds / 3600) > 0) {
|
if ((num = seconds / 3600) > 0) {
|
||||||
s << num << " hours, ";
|
s << num << " " << tr("hours", num) << ", ";
|
||||||
seconds -= num * 3600;
|
seconds -= num * 3600;
|
||||||
}
|
}
|
||||||
if ((num = seconds / 60) > 0) {
|
if ((num = seconds / 60) > 0) {
|
||||||
s << num << " min, ";
|
s << num << " " << tr("minutes", num) << ", ";
|
||||||
seconds -= num * 60;
|
seconds -= num * 60;
|
||||||
}
|
}
|
||||||
s << seconds << " seconds";
|
s << seconds << " " << tr("seconds", seconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ShowTraffic (std::stringstream& s, uint64_t bytes)
|
static void ShowTraffic (std::stringstream& s, uint64_t bytes)
|
||||||
|
@ -197,11 +217,7 @@ namespace http {
|
||||||
"<!DOCTYPE html>\r\n"
|
"<!DOCTYPE html>\r\n"
|
||||||
"<html lang=\"en\">\r\n" /* TODO: Add support for locale */
|
"<html lang=\"en\">\r\n" /* TODO: Add support for locale */
|
||||||
" <head>\r\n" /* TODO: Find something to parse html/template system. This is horrible. */
|
" <head>\r\n" /* TODO: Find something to parse html/template system. This is horrible. */
|
||||||
#if (!defined(WIN32))
|
|
||||||
" <meta charset=\"UTF-8\">\r\n"
|
" <meta charset=\"UTF-8\">\r\n"
|
||||||
#else
|
|
||||||
" <meta charset=\"windows-1251\">\r\n"
|
|
||||||
#endif
|
|
||||||
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n"
|
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n"
|
||||||
" <link rel=\"shortcut icon\" href=\"" << itoopieFavicon << "\">\r\n"
|
" <link rel=\"shortcut icon\" href=\"" << itoopieFavicon << "\">\r\n"
|
||||||
" <title>Purple I2P " VERSION " Webconsole</title>\r\n"
|
" <title>Purple I2P " VERSION " Webconsole</title>\r\n"
|
||||||
|
@ -315,7 +331,7 @@ namespace http {
|
||||||
s << "<b>Transit:</b> ";
|
s << "<b>Transit:</b> ";
|
||||||
ShowTraffic (s, i2p::transport::transports.GetTotalTransitTransmittedBytes ());
|
ShowTraffic (s, i2p::transport::transports.GetTotalTransitTransmittedBytes ());
|
||||||
s << " (" << (double) i2p::transport::transports.GetTransitBandwidth () / 1024 << " KiB/s)<br>\r\n";
|
s << " (" << (double) i2p::transport::transports.GetTransitBandwidth () / 1024 << " KiB/s)<br>\r\n";
|
||||||
s << "<b>Data path:</b> " << i2p::fs::GetDataDir() << "<br>\r\n";
|
s << "<b>Data path:</b> " << i2p::http::DataPath << "<br>\r\n";
|
||||||
s << "<div class='slide'>";
|
s << "<div class='slide'>";
|
||||||
if((outputFormat==OutputFormatEnum::forWebConsole)||!includeHiddenContent) {
|
if((outputFormat==OutputFormatEnum::forWebConsole)||!includeHiddenContent) {
|
||||||
s << "<label for=\"slide-info\">Hidden content. Press on text to see.</label>\r\n<input type=\"checkbox\" id=\"slide-info\" />\r\n<div class=\"slidecontent\">\r\n";
|
s << "<label for=\"slide-info\">Hidden content. Press on text to see.</label>\r\n<input type=\"checkbox\" id=\"slide-info\" />\r\n<div class=\"slidecontent\">\r\n";
|
||||||
|
@ -1374,6 +1390,8 @@ namespace http {
|
||||||
LogPrint(eLogInfo, "HTTPServer: password set to ", pass);
|
LogPrint(eLogInfo, "HTTPServer: password set to ", pass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i2p::http::SetDataDir();
|
||||||
|
|
||||||
m_IsRunning = true;
|
m_IsRunning = true;
|
||||||
m_Thread.reset (new std::thread (std::bind (&HTTPServer::Run, this)));
|
m_Thread.reset (new std::thread (std::bind (&HTTPServer::Run, this)));
|
||||||
m_Acceptor.listen ();
|
m_Acceptor.listen ();
|
||||||
|
|
|
@ -19,4 +19,6 @@ LIB_CLIENT_SRC = $(wildcard $(LIB_CLIENT_SRC_DIR)/*.cpp)
|
||||||
#DAEMON_SRC = \
|
#DAEMON_SRC = \
|
||||||
# HTTPServer.cpp I2PControl.cpp UPnP.cpp Daemon.cpp i2pd.cpp
|
# HTTPServer.cpp I2PControl.cpp UPnP.cpp Daemon.cpp i2pd.cpp
|
||||||
|
|
||||||
|
LANG_SRC = $(wildcard $(LANG_SRC_DIR)/*.cpp)
|
||||||
|
|
||||||
DAEMON_SRC = $(wildcard $(DAEMON_SRC_DIR)/*.cpp)
|
DAEMON_SRC = $(wildcard $(DAEMON_SRC_DIR)/*.cpp)
|
||||||
|
|
39
i18n/I18N.h
Normal file
39
i18n/I18N.h
Normal file
|
@ -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<typename inttype>
|
||||||
|
std::string translate (std::string arg, inttype&& n)
|
||||||
|
{
|
||||||
|
return i2p::i18n::russian::GetPlural (arg, (int) n);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // i18n
|
||||||
|
} // i2p
|
||||||
|
|
||||||
|
template<typename... TArgs>
|
||||||
|
std::string tr (TArgs&&... args) {
|
||||||
|
return i2p::i18n::translate(std::forward<TArgs>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // __I18N_H__
|
56
i18n/russian.cpp
Normal file
56
i18n/russian.cpp
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// 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<std::string, std::string> strings
|
||||||
|
{
|
||||||
|
{"Enabled", "Включено"},
|
||||||
|
{"Disabled", "Выключено"}
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::map<std::string, std::vector<std::string>> 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
|
|
@ -47,10 +47,10 @@ namespace fs {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
char localAppData[MAX_PATH];
|
wchar_t localAppData[MAX_PATH];
|
||||||
|
|
||||||
// check executable directory first
|
// check executable directory first
|
||||||
if(!GetModuleFileName(NULL, localAppData, MAX_PATH))
|
if(!GetModuleFileNameW(NULL, localAppData, MAX_PATH))
|
||||||
{
|
{
|
||||||
#ifdef WIN32_APP
|
#ifdef WIN32_APP
|
||||||
MessageBox(NULL, TEXT("Unable to get application path!"), TEXT("I2Pd: error"), MB_ICONERROR | MB_OK);
|
MessageBox(NULL, TEXT("Unable to get application path!"), TEXT("I2Pd: error"), MB_ICONERROR | MB_OK);
|
||||||
|
@ -61,14 +61,15 @@ namespace fs {
|
||||||
}
|
}
|
||||||
else
|
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 config file exists in .exe's folder use it
|
||||||
if(boost::filesystem::exists(execPath/"i2pd.conf")) // TODO: magic string
|
if(boost::filesystem::exists(execPath/"i2pd.conf")) // TODO: magic string
|
||||||
dataDir = execPath.string ();
|
|
||||||
else // otherwise %appdata%
|
|
||||||
{
|
{
|
||||||
if(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, localAppData) != S_OK)
|
dataDir = execPath.string ();
|
||||||
|
} else // otherwise %appdata%
|
||||||
|
{
|
||||||
|
if(SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, 0, localAppData) != S_OK)
|
||||||
{
|
{
|
||||||
#ifdef WIN32_APP
|
#ifdef WIN32_APP
|
||||||
MessageBox(NULL, TEXT("Unable to get AppData path!"), TEXT("I2Pd: error"), MB_ICONERROR | MB_OK);
|
MessageBox(NULL, TEXT("Unable to get AppData path!"), TEXT("I2Pd: error"), MB_ICONERROR | MB_OK);
|
||||||
|
@ -78,7 +79,9 @@ namespace fs {
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
dataDir = std::string(localAppData) + "\\" + appName;
|
{
|
||||||
|
dataDir = boost::filesystem::wpath(localAppData).string() + "\\" + appName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
Loading…
Reference in a new issue