languages support (WIP)

This commit is contained in:
MihailRis
2023-12-12 22:02:17 +03:00
parent 0837cf4f03
commit 7bf70bb26e
21 changed files with 456 additions and 38 deletions
+18 -3
View File
@@ -98,6 +98,18 @@ void BasicParser::skipWhitespace() {
}
}
void BasicParser::skipLine() {
while (hasNext()) {
if (source[pos] == '\n') {
pos++;
linestart = pos;
line++;
break;
}
pos++;
}
}
bool BasicParser::hasNext() {
return pos < source.length();
}
@@ -250,7 +262,7 @@ bool BasicParser::parseNumber(int sign, number_u& out) {
return true;
}
string BasicParser::parseString(char quote) {
string BasicParser::parseString(char quote, bool closeRequired) {
std::stringstream ss;
while (hasNext()) {
char c = source[pos];
@@ -282,13 +294,16 @@ string BasicParser::parseString(char quote) {
}
continue;
}
if (c == '\n') {
if (c == '\n' && closeRequired) {
throw error("non-closed string literal");
}
ss << c;
pos++;
}
throw error("unexpected end");
if (closeRequired) {
throw error("unexpected end");
}
return ss.str();
}
parsing_error BasicParser::error(std::string message) {
+2 -1
View File
@@ -70,6 +70,7 @@ protected:
uint linestart = 0;
virtual void skipWhitespace();
void skipLine();
void expect(char expected);
char peek();
char nextChar();
@@ -79,7 +80,7 @@ protected:
std::string parseName();
int64_t parseSimpleInt(int base);
bool parseNumber(int sign, number_u& out);
std::string parseString(char chr);
std::string parseString(char chr, bool closeRequired=true);
parsing_error error(std::string message);
+1 -10
View File
@@ -115,16 +115,7 @@ Reader::Reader(Wrapper* wrapper, string file, string source) : BasicParser(file,
void Reader::skipWhitespace() {
BasicParser::skipWhitespace();
if (hasNext() && source[pos] == '#') {
pos++;
while (hasNext()) {
if (source[pos] == '\n') {
pos++;
linestart = pos;
line++;
break;
}
pos++;
}
skipLine();
if (hasNext() && is_whitespace(peek())) {
skipWhitespace();
}
+2 -1
View File
@@ -5,7 +5,7 @@
#include "typedefs.h"
const int ENGINE_VERSION_MAJOR = 0;
const int ENGINE_VERSION_MINOR = 15;
const int ENGINE_VERSION_MINOR = 16;
const int CHUNK_W = 16;
const int CHUNK_H = 256;
@@ -16,6 +16,7 @@ const int CHUNK_VOL = (CHUNK_W * CHUNK_H * CHUNK_D);
/* BLOCK_VOID is block id used to mark non-existing voxel (voxel of missing chunk) */
const blockid_t BLOCK_VOID = std::numeric_limits<blockid_t>::max();
const blockid_t MAX_BLOCKS = BLOCK_VOID;
inline uint vox_index(int x, int y, int z, int w=CHUNK_W, int d=CHUNK_D) {
return (y * d + z) * w + x;
+17
View File
@@ -0,0 +1,17 @@
#include "ContentPack.h"
using std::string;
using std::filesystem::path;
ContentPack::ContentPack(const string id,
const path folder)
: id(id), folder(folder) {
}
const string& ContentPack::getId() const {
return id;
}
const path& ContentPack::getFolder() const {
return folder;
}
+18
View File
@@ -0,0 +1,18 @@
#ifndef CONTENT_CONTENT_PACK_H_
#define CONTENT_CONTENT_PACK_H_
#include <string>
#include <filesystem>
class ContentPack {
const std::string id;
const std::filesystem::path folder;
public:
ContentPack(const std::string id,
const std::filesystem::path folder);
const std::string& getId() const;
const std::filesystem::path& getFolder() const;
};
#endif // CONTENT_CONTENT_PACK_H_
+14
View File
@@ -3,6 +3,7 @@
#include <memory>
#include <iostream>
#include <assert.h>
#include <vector>
#include <glm/glm.hpp>
#include <filesystem>
#define GLEW_STATIC
@@ -27,6 +28,9 @@
#include "files/files.h"
#include "files/engine_paths.h"
#include "content/ContentPack.h"
#include "frontend/locale/langs.h"
using std::unique_ptr;
using std::shared_ptr;
using std::string;
@@ -55,6 +59,16 @@ Engine::Engine(EngineSettings& settings, EnginePaths* paths, Content* content)
}
Audio::initialize();
gui = new GUI();
std::vector<const ContentPack*> packs;
auto resdir = paths->getResources();
auto base = std::make_unique<ContentPack>("base", resdir/path("content/base"));
packs.push_back(base.get());
if (settings.ui.language == "auto") {
settings.ui.language = platform::detect_locale();
}
langs::setup(resdir, settings.ui.language, packs);
std::cout << "-- initializing finished" << std::endl;
}
+3
View File
@@ -39,6 +39,9 @@ toml::Wrapper create_wrapper(EngineSettings& settings) {
debug.add("generator-test-mode", &settings.debug.generatorTestMode);
debug.add("show-chunk-borders", &settings.debug.showChunkBorders);
debug.add("do-write-lights", &settings.debug.doWriteLights);
toml::Section& ui = wrapper.add("ui");
ui.add("language", &settings.ui.language);
return wrapper;
}
+7 -2
View File
@@ -4,6 +4,8 @@
#include <glm/glm.hpp>
#include "../locale/langs.h"
using namespace gui;
using glm::vec2;
using glm::vec4;
@@ -11,7 +13,7 @@ using std::string;
using std::wstring;
Button* guiutil::backButton(PagesControl* menu) {
return (new Button(L"Back", vec4(10.f)))->listenAction([=](GUI* gui) {
return (new Button(langs::get(L"Back"), vec4(10.f)))->listenAction([=](GUI* gui) {
menu->back();
});
}
@@ -27,7 +29,7 @@ void guiutil::alert(GUI* gui, wstring text, gui::runnable on_hidden) {
Panel* panel = new Panel(vec2(500, 200), vec4(8.0f), 8.0f);
panel->color(vec4(0.0f, 0.0f, 0.0f, 0.5f));
panel->add(new Label(text));
panel->add((new Button(L"Ok", vec4(10.f)))->listenAction([=](GUI* gui) {
panel->add((new Button(langs::get(L"Ok"), vec4(10.f)))->listenAction([=](GUI* gui) {
if (on_hidden)
on_hidden();
menu->back();
@@ -39,6 +41,9 @@ void guiutil::alert(GUI* gui, wstring text, gui::runnable on_hidden) {
void guiutil::confirm(GUI* gui, wstring text, gui::runnable on_confirm,
wstring yestext, wstring notext) {
if (yestext.empty()) yestext = langs::get(L"Yes");
if (notext.empty()) notext = langs::get(L"No");
PagesControl* menu = gui->getMenu();
Panel* panel = new Panel(vec2(600, 200), vec4(8.0f), 8.0f);
panel->color(vec4(0.0f, 0.0f, 0.0f, 0.5f));
+1 -1
View File
@@ -13,7 +13,7 @@ namespace guiutil {
gui::Button* gotoButton(std::wstring text, std::string page, gui::PagesControl* menu);
void alert(gui::GUI* gui, std::wstring text, gui::runnable on_hidden=nullptr);
void confirm(gui::GUI* gui, std::wstring text, gui::runnable on_confirm=nullptr,
std::wstring yestext=L"Yes", std::wstring notext=L"No");
std::wstring yestext=L"", std::wstring notext=L"");
}
#endif // FRONTEND_GUI_GUI_UTIL_H_
+133
View File
@@ -0,0 +1,133 @@
#include "langs.h"
#include <iostream>
#include "../../coders/json.h"
#include "../../coders/commons.h"
#include "../../content/ContentPack.h"
#include "../../files/files.h"
#include "../../util/stringutil.h"
using std::string;
using std::wstring;
using std::vector;
using std::unique_ptr;
using std::unordered_map;
using std::filesystem::path;
namespace fs = std::filesystem;
unique_ptr<langs::Lang> langs::current;
vector<langs::LocaleInfo> langs::locales_info;
langs::Lang::Lang(string locale) : locale(locale) {
}
const wstring& langs::Lang::get(const wstring& key) const {
auto found = map.find(key);
if (found == map.end()) {
return key;
}
return found->second;
}
void langs::Lang::put(const wstring& key, const wstring& text) {
map[key] = text;
}
/* Language key-value txt files parser */
class Reader : public BasicParser {
void skipWhitespace() override {
BasicParser::skipWhitespace();
if (hasNext() && source[pos] == '#') {
skipLine();
if (hasNext() && is_whitespace(peek())) {
skipWhitespace();
}
}
}
public:
Reader(string file, string source) : BasicParser(file, source) {
}
void read(langs::Lang& lang, std::string prefix) {
skipWhitespace();
while (hasNext()) {
string key = parseString('=', true);
util::trim(key);
key = prefix + key;
string text = parseString('\n', false);
util::trim(text);
lang.put(util::str2wstr_utf8(key),
util::str2wstr_utf8(text));
skipWhitespace();
}
}
};
void langs::loadLocalesInfo(const path& resdir, string& fallback) {
path file = resdir/path(langs::TEXTS_FOLDER)/path("langs.json");
unique_ptr<json::JObject> root (files::read_json(file));
langs::locales_info.clear();
root->str("fallback", fallback);
auto langs = root->obj("langs");
if (langs) {
for (auto& entry : langs->map) {
auto langInfo = entry.second;
string name;
if (langInfo->type == json::valtype::object) {
name = langInfo->value.obj->getStr("name", "none");
} else {
continue;
}
std::cout << "locale " << entry.first << " (" << name << ") added" << std::endl;
langs::locales_info.push_back(LocaleInfo {entry.first, name});
}
}
}
void langs::load(const path& resdir,
const string& locale,
vector<const ContentPack*>& packs,
Lang& lang) {
path filename = path(TEXTS_FOLDER)/path(locale + LANG_FILE_EXT);
path core_file = resdir/filename;
if (fs::is_regular_file(core_file)) {
string text = files::read_string(core_file);
Reader reader(core_file.string(), text);
reader.read(lang, "");
}
for (auto pack : packs) {
path file = pack->getFolder()/filename;
if (fs::is_regular_file(file)) {
string text = files::read_string(file);
Reader reader(file.string(), text);
reader.read(lang, pack->getId()+":");
}
}
}
void langs::load(const path& resdir,
const string& locale,
const string& fallback,
vector<const ContentPack*>& packs) {
unique_ptr<Lang> lang (new Lang(locale));
load(resdir, fallback, packs, *lang.get());
load(resdir, locale, packs, *lang.get());
current.reset(lang.release());
}
void langs::setup(const path& resdir,
const string& locale,
vector<const ContentPack*>& packs) {
string fallback = langs::FALLBACK_DEFAULT;
langs::loadLocalesInfo(resdir, fallback);
langs::load(resdir, locale, fallback, packs);
}
const wstring& langs::get(const wstring& key) {
return current->get(key);
}
+65
View File
@@ -0,0 +1,65 @@
#ifndef FRONTEND_LOCALE_LANGS_H
#define FRONTEND_LOCALE_LANGS_H
#include <string>
#include <vector>
#include <memory>
#include <filesystem>
#include <unordered_map>
class ContentPack;
namespace langs {
const char LANG_FILE_EXT[] = ".txt";
const char TEXTS_FOLDER[] = "texts";
const char FALLBACK_DEFAULT[] = "en_US";
/*
Translation is mostly used for rendered text,
so Font.draw and Lang both use wstring for strings.
Translation key is wstring too, allowing to use it as value
if no any translation found, without conversion required
Key syntax: `modid:context.key` where modid is added at runtime for
content pack texts
*/
class Lang {
std::string locale;
std::unordered_map<std::wstring, std::wstring> map;
public:
Lang(std::string locale);
const std::wstring& get(const std::wstring& key) const;
void put(const std::wstring& key, const std::wstring& text);
};
struct LocaleInfo {
std::string locale;
std::string name;
};
extern std::unique_ptr<Lang> current;
extern std::vector<LocaleInfo> locales_info;
extern void loadLocalesInfo(
const std::filesystem::path& resdir,
std::string& fallback);
extern void load(const std::filesystem::path& resdir,
const std::string& locale,
std::vector<const ContentPack*>& packs,
Lang& lang);
extern void load(const std::filesystem::path& resdir,
const std::string& locale,
const std::string& fallback,
std::vector<const ContentPack*>& packs);
extern const std::wstring& get(const std::wstring& key);
extern void setup(const std::filesystem::path& resdir,
const std::string& locale,
std::vector<const ContentPack*>& packs);
}
#endif // FRONTEND_LOCALE_LANGS_H
+21 -20
View File
@@ -23,6 +23,7 @@
#include "../content/ContentLUT.h"
#include "gui/gui_util.h"
#include "locale/langs.h"
using glm::vec2;
using glm::vec4;
@@ -41,7 +42,7 @@ void show_content_missing(GUI* gui, const Content* content, ContentLUT* lut) {
PagesControl* menu = gui->getMenu();
Panel* panel = new Panel(vec2(500, 200), vec4(8.0f), 8.0f);
panel->color(vec4(0.0f, 0.0f, 0.0f, 0.5f));
panel->add(new Label(L"Content missing!"));
panel->add(new Label(langs::get(L"menu.missing-content")));
Panel* subpanel = new Panel(vec2(500, 100));
subpanel->color(vec4(0.0f, 0.0f, 0.0f, 0.5f));
@@ -67,7 +68,7 @@ void show_content_missing(GUI* gui, const Content* content, ContentLUT* lut) {
subpanel->maxLength(400);
panel->add(subpanel);
panel->add((new Button(L"Back to Main Menu", vec4(8.0f)))->listenAction([=](GUI*){
panel->add((new Button(langs::get(L"menu.back-to-menu"), vec4(8.0f)))->listenAction([=](GUI*){
menu->back();
}));
panel->refresh();
@@ -97,7 +98,7 @@ Panel* create_main_menu_panel(Engine* engine, PagesControl* menu) {
Panel* panel = new Panel(vec2(400, 200), vec4(5.0f), 1.0f);
panel->color(vec4(0.0f));
panel->add(guiutil::gotoButton(L"New World", "new-world", menu));
panel->add(guiutil::gotoButton(langs::get(L"menu.new-world"), "new-world", menu));
Panel* worldsPanel = new Panel(vec2(390, 200), vec4(5.0f));
worldsPanel->color(vec4(1.0f, 1.0f, 1.0f, 0.07f));
@@ -135,8 +136,8 @@ Panel* create_main_menu_panel(Engine* engine, PagesControl* menu) {
}
}
panel->add(worldsPanel);
panel->add(guiutil::gotoButton(L"Settings", "settings", menu));
panel->add((new Button(L"Quit", vec4(10.f)))->listenAction([](GUI* gui) {
panel->add(guiutil::gotoButton(langs::get(L"menu.settings"), "settings", menu));
panel->add((new Button(langs::get(L"menu.quit"), vec4(10.f)))->listenAction([](GUI* gui) {
Window::setShouldClose(true);
}));
panel->refresh();
@@ -149,7 +150,7 @@ Panel* create_new_world_panel(Engine* engine, PagesControl* menu) {
TextBox* worldNameInput;
{
Label* label = new Label(L"World Name");
Label* label = new Label(langs::get(L"world.name"));
panel->add(label);
TextBox* input = new TextBox(L"New World", vec4(6.0f));
@@ -159,7 +160,7 @@ Panel* create_new_world_panel(Engine* engine, PagesControl* menu) {
TextBox* seedInput;
{
Label* label = new Label(L"Seed");
Label* label = new Label(langs::get(L"world.seed"));
panel->add(shared_ptr<UINode>(label));
uint64_t randseed = rand() ^ (rand() << 8) ^
@@ -173,7 +174,7 @@ Panel* create_new_world_panel(Engine* engine, PagesControl* menu) {
}
{
Button* button = new Button(L"Create World", vec4(10.0f));
Button* button = new Button(langs::get(L"world.create"), vec4(10.0f));
button->margin(vec4(1, 20, 1, 1));
vec4 basecolor = worldNameInput->color();
button->listenAction([=](GUI*) {
@@ -239,7 +240,7 @@ Panel* create_controls_panel(Engine* engine, PagesControl* menu) {
std::wstringstream ss;
ss << std::fixed << std::setprecision(1);
ss << engine->getSettings().camera.sensitivity;
return L"Mouse Sensitivity: "+ss.str();
return langs::get(L"mouse.sensitivity")+L": "+ss.str();
}));
TrackBar* trackbar = new TrackBar(0.1, 10.0, 2.0, 0.1, 4);
@@ -264,7 +265,7 @@ Panel* create_controls_panel(Engine* engine, PagesControl* menu) {
InputBindBox* bindbox = new InputBindBox(entry.second);
subpanel->add(bindbox);
Label* label = new Label(util::str2wstr_utf8(bindname));
Label* label = new Label(langs::get(util::str2wstr_utf8(bindname)));
label->margin(vec4(6.0f));
subpanel->add(label);
scrollPanel->add(subpanel);
@@ -282,7 +283,7 @@ Panel* create_settings_panel(Engine* engine, PagesControl* menu) {
/* Load Distance setting track bar */{
panel->add((new Label(L""))->textSupplier([=]() {
return L"Load Distance: " +
return langs::get(L"chunks.load-distance")+L": " +
std::to_wstring(engine->getSettings().chunks.loadDistance);
}));
@@ -298,7 +299,7 @@ Panel* create_settings_panel(Engine* engine, PagesControl* menu) {
/* Load Speed setting track bar */{
panel->add((new Label(L""))->textSupplier([=]() {
return L"Load Speed: " +
return langs::get(L"chunks.load-speed")+L": " +
std::to_wstring(engine->getSettings().chunks.loadSpeed);
}));
@@ -317,7 +318,7 @@ Panel* create_settings_panel(Engine* engine, PagesControl* menu) {
std::wstringstream ss;
ss << std::fixed << std::setprecision(1);
ss << engine->getSettings().graphics.fogCurve;
return L"Fog Curve: " + ss.str();
return langs::get(L"graphics.fog-curve")+L": " + ss.str();
}));
TrackBar* trackbar = new TrackBar(1.0, 6.0, 1.0, 0.1, 2);
@@ -333,7 +334,7 @@ Panel* create_settings_panel(Engine* engine, PagesControl* menu) {
/* Fov setting track bar */{
panel->add((new Label(L""))->textSupplier([=]() {
int fov = (int)engine->getSettings().camera.fov;
return L"FOV: "+std::to_wstring(fov)+L"°";
return langs::get(L"camera.fov")+L": "+std::to_wstring(fov)+L"°";
}));
TrackBar* trackbar = new TrackBar(30.0, 120.0, 90, 1, 4);
@@ -360,7 +361,7 @@ Panel* create_settings_panel(Engine* engine, PagesControl* menu) {
engine->getSettings().display.swapInterval = checked;
});
checkpanel->add(checkbox);
checkpanel->add(new Label(L"V-Sync"));
checkpanel->add(new Label(langs::get(L"display.vsync")));
panel->add(checkpanel);
}
@@ -379,12 +380,12 @@ Panel* create_settings_panel(Engine* engine, PagesControl* menu) {
engine->getSettings().graphics.backlight = checked;
});
checkpanel->add(checkbox);
checkpanel->add(new Label(L"Backlight"));
checkpanel->add(new Label(langs::get(L"graphics.backlight")));
panel->add(checkpanel);
}
panel->add(guiutil::gotoButton(L"Controls", "controls", menu));
panel->add(guiutil::gotoButton(langs::get(L"menu.controls"), "controls", menu));
panel->add(guiutil::backButton(menu));
panel->refresh();
return panel;
@@ -394,15 +395,15 @@ Panel* create_pause_panel(Engine* engine, PagesControl* menu) {
Panel* panel = new Panel(vec2(400, 200));
panel->color(vec4(0.0f));
{
Button* button = new Button(L"Continue", vec4(10.0f));
Button* button = new Button(langs::get(L"Continue"), vec4(10.0f));
button->listenAction([=](GUI*){
menu->reset();
});
panel->add(shared_ptr<UINode>(button));
}
panel->add(guiutil::gotoButton(L"Settings", "settings", menu));
panel->add(guiutil::gotoButton(langs::get(L"Settings"), "settings", menu));
{
Button* button = new Button(L"Save and Quit to Menu", vec4(10.f));
Button* button = new Button(langs::get(L"menu.save-and-quit"), vec4(10.f));
button->listenAction([engine](GUI*){
engine->setScreen(shared_ptr<Screen>(new MenuScreen(engine)));
});
+5
View File
@@ -61,12 +61,17 @@ struct DebugSettings {
bool doWriteLights = true;
};
struct UiSettings {
std::string language = "auto";
};
struct EngineSettings {
DisplaySettings display;
ChunksSettings chunks;
CameraSettings camera;
GraphicsSettings graphics;
DebugSettings debug;
UiSettings ui;
};
#endif // SRC_SETTINGS_H_
+9
View File
@@ -20,6 +20,15 @@ path platform::get_controls_file() {
return path(CONTROLS_FILE);
}
std::string platform::detect_locale() {
// TODO: implement
std::string name = setlocale(LC_ALL, nullptr);
if (name.find("ru_RU") != std::string::npos) {
return "ru_RU";
}
return "en_US";
}
#ifdef WIN32
#include <Windows.h>
+1
View File
@@ -8,6 +8,7 @@ namespace platform {
extern void configure_encoding();
extern std::filesystem::path get_settings_file();
extern std::filesystem::path get_controls_file();
extern std::string detect_locale();
}
#endif // UTIL_PLATFORM_H_