languages support (WIP)
This commit is contained in:
parent
0837cf4f03
commit
7bf70bb26e
22
res/content/base/texts/en_US.txt
Normal file
22
res/content/base/texts/en_US.txt
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
bazalt=Bazalt
|
||||||
|
blue_lamp=Blue Lamp
|
||||||
|
brick=Brick
|
||||||
|
dirt=Dirt
|
||||||
|
flower=Flower
|
||||||
|
glass=Glass
|
||||||
|
grass_block=Ground
|
||||||
|
grass=Tall Grass
|
||||||
|
green_lamp=Green Lamp
|
||||||
|
lamp=Lamp
|
||||||
|
leaves=Leaves
|
||||||
|
lightbulb=Bulb
|
||||||
|
metal=Metal
|
||||||
|
pane=Pane
|
||||||
|
pipe=Pipe
|
||||||
|
planks=Planks
|
||||||
|
red_lamp=Red Lamp
|
||||||
|
rust=Rust
|
||||||
|
sand=Sand
|
||||||
|
stone=Stone
|
||||||
|
water=Water
|
||||||
|
wood=Wood
|
||||||
22
res/content/base/texts/ru_RU.txt
Normal file
22
res/content/base/texts/ru_RU.txt
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
bazalt=Базальт
|
||||||
|
blue_lamp=Синяя Лампа
|
||||||
|
brick=Кирпич
|
||||||
|
dirt=Земля
|
||||||
|
flower=Цветок
|
||||||
|
glass=Стекло
|
||||||
|
grass_block=Дёрн
|
||||||
|
grass=Высокая Трава
|
||||||
|
green_lamp=Зелёная Лампа
|
||||||
|
lamp=Лампа
|
||||||
|
leaves=Листва
|
||||||
|
lightbulb=Лампочка
|
||||||
|
metal=Металл
|
||||||
|
pane=Панель
|
||||||
|
pipe=Труба
|
||||||
|
planks=Доски
|
||||||
|
red_lamp=Красная Лампа
|
||||||
|
rust=Ржавчина
|
||||||
|
sand=Песок
|
||||||
|
stone=Камень
|
||||||
|
water=Вода
|
||||||
|
wood=Бревно
|
||||||
39
res/texts/en_US.txt
Normal file
39
res/texts/en_US.txt
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# Menu
|
||||||
|
menu.new-world=New World
|
||||||
|
menu.quit=Quit
|
||||||
|
menu.create-world=Create World
|
||||||
|
menu.save-and-quit=Save and Quit to Menu
|
||||||
|
menu.missing-content=Missing Content!
|
||||||
|
menu.controls=Controls
|
||||||
|
menu.back-to-menu=Back to Main Menu
|
||||||
|
menu.settings=Settings
|
||||||
|
world.seed=Seed
|
||||||
|
world.name=World Name
|
||||||
|
world.create=Create World
|
||||||
|
|
||||||
|
# Settings
|
||||||
|
chunks.load-distance=Load Distance
|
||||||
|
chunks.load-speed=Load Speed
|
||||||
|
graphics.fog-curve=Fog Curve
|
||||||
|
graphics.backlight=Backlight
|
||||||
|
display.vsync=V-Sync
|
||||||
|
camera.fov=FOV
|
||||||
|
mouse.sensitivity=Mouse Sensitivity
|
||||||
|
|
||||||
|
# Bindings
|
||||||
|
movement.forward=Forward
|
||||||
|
movement.back=Back
|
||||||
|
movement.left=Left
|
||||||
|
movement.right=Right
|
||||||
|
movement.jump=Jump
|
||||||
|
movement.sprint=Sprint
|
||||||
|
movement.crouch=Crouch
|
||||||
|
movement.cheat=Cheat
|
||||||
|
hud.inventory=Inventory
|
||||||
|
player.pick=Pick Block
|
||||||
|
player.attack=Attack / Break
|
||||||
|
player.build=Place Block
|
||||||
|
player.flight=Flight
|
||||||
|
player.noclip=No-clip
|
||||||
|
camera.zoom=Zoom
|
||||||
|
camera.mode=Switch Camera Mode
|
||||||
11
res/texts/langs.json
Normal file
11
res/texts/langs.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"langs": {
|
||||||
|
"en_US": {
|
||||||
|
"name": "English"
|
||||||
|
},
|
||||||
|
"ru_RU": {
|
||||||
|
"name": "Русский"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fallback": "en_US"
|
||||||
|
}
|
||||||
45
res/texts/ru_RU.txt
Normal file
45
res/texts/ru_RU.txt
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# Общее
|
||||||
|
Yes=Да
|
||||||
|
No=Нет
|
||||||
|
Ok=Ок
|
||||||
|
Back=Назад
|
||||||
|
Continue=Продолжить
|
||||||
|
|
||||||
|
# Меню
|
||||||
|
menu.new-world=Новый Мир
|
||||||
|
menu.quit=Выход
|
||||||
|
menu.continue=Продолжить
|
||||||
|
menu.save-and-quit=Сохранить и Выйти в Меню
|
||||||
|
menu.missing-content=Отсутствует Контент!
|
||||||
|
menu.controls=Управление
|
||||||
|
menu.back-to-menu=Вернуться в Меню
|
||||||
|
menu.settings=Настройки
|
||||||
|
world.seed=Зерно
|
||||||
|
world.name=Название
|
||||||
|
world.create=Создать Мир
|
||||||
|
|
||||||
|
# Настройки
|
||||||
|
chunks.load-distance=Дистанция Загрузки
|
||||||
|
chunks.load-speed=Скорость Загрузки
|
||||||
|
graphics.fog-curve=Кривая Тумана
|
||||||
|
graphics.backlight=Подсветка
|
||||||
|
display.vsync=V-Sync
|
||||||
|
camera.fov=Поле Зрения
|
||||||
|
mouse.sensitivity=Чувствительность Мыши
|
||||||
|
|
||||||
|
# Управление
|
||||||
|
movement.forward=Вперёд
|
||||||
|
movement.back=Назад
|
||||||
|
movement.left=Влево
|
||||||
|
movement.right=Вправо
|
||||||
|
movement.jump=Прыжок
|
||||||
|
movement.sprint=Ускорение
|
||||||
|
movement.crouch=Красться
|
||||||
|
movement.cheat=Чит
|
||||||
|
hud.inventory=Инвентарь
|
||||||
|
player.pick=Подобрать Блок
|
||||||
|
player.attack=Атаковать / Сломать
|
||||||
|
player.build=Поставить Блок
|
||||||
|
player.flight=Полёт
|
||||||
|
camera.zoom=Приближение
|
||||||
|
camera.mode=Сменить Режим Камеры
|
||||||
@ -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() {
|
bool BasicParser::hasNext() {
|
||||||
return pos < source.length();
|
return pos < source.length();
|
||||||
}
|
}
|
||||||
@ -250,7 +262,7 @@ bool BasicParser::parseNumber(int sign, number_u& out) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
string BasicParser::parseString(char quote) {
|
string BasicParser::parseString(char quote, bool closeRequired) {
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
while (hasNext()) {
|
while (hasNext()) {
|
||||||
char c = source[pos];
|
char c = source[pos];
|
||||||
@ -282,14 +294,17 @@ string BasicParser::parseString(char quote) {
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (c == '\n') {
|
if (c == '\n' && closeRequired) {
|
||||||
throw error("non-closed string literal");
|
throw error("non-closed string literal");
|
||||||
}
|
}
|
||||||
ss << c;
|
ss << c;
|
||||||
pos++;
|
pos++;
|
||||||
}
|
}
|
||||||
|
if (closeRequired) {
|
||||||
throw error("unexpected end");
|
throw error("unexpected end");
|
||||||
}
|
}
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
parsing_error BasicParser::error(std::string message) {
|
parsing_error BasicParser::error(std::string message) {
|
||||||
return parsing_error(message, filename, source, pos, line, linestart);
|
return parsing_error(message, filename, source, pos, line, linestart);
|
||||||
|
|||||||
@ -70,6 +70,7 @@ protected:
|
|||||||
uint linestart = 0;
|
uint linestart = 0;
|
||||||
|
|
||||||
virtual void skipWhitespace();
|
virtual void skipWhitespace();
|
||||||
|
void skipLine();
|
||||||
void expect(char expected);
|
void expect(char expected);
|
||||||
char peek();
|
char peek();
|
||||||
char nextChar();
|
char nextChar();
|
||||||
@ -79,7 +80,7 @@ protected:
|
|||||||
std::string parseName();
|
std::string parseName();
|
||||||
int64_t parseSimpleInt(int base);
|
int64_t parseSimpleInt(int base);
|
||||||
bool parseNumber(int sign, number_u& out);
|
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);
|
parsing_error error(std::string message);
|
||||||
|
|
||||||
|
|||||||
@ -115,16 +115,7 @@ Reader::Reader(Wrapper* wrapper, string file, string source) : BasicParser(file,
|
|||||||
void Reader::skipWhitespace() {
|
void Reader::skipWhitespace() {
|
||||||
BasicParser::skipWhitespace();
|
BasicParser::skipWhitespace();
|
||||||
if (hasNext() && source[pos] == '#') {
|
if (hasNext() && source[pos] == '#') {
|
||||||
pos++;
|
skipLine();
|
||||||
while (hasNext()) {
|
|
||||||
if (source[pos] == '\n') {
|
|
||||||
pos++;
|
|
||||||
linestart = pos;
|
|
||||||
line++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
pos++;
|
|
||||||
}
|
|
||||||
if (hasNext() && is_whitespace(peek())) {
|
if (hasNext() && is_whitespace(peek())) {
|
||||||
skipWhitespace();
|
skipWhitespace();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
#include "typedefs.h"
|
#include "typedefs.h"
|
||||||
|
|
||||||
const int ENGINE_VERSION_MAJOR = 0;
|
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_W = 16;
|
||||||
const int CHUNK_H = 256;
|
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) */
|
/* 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 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) {
|
inline uint vox_index(int x, int y, int z, int w=CHUNK_W, int d=CHUNK_D) {
|
||||||
return (y * d + z) * w + x;
|
return (y * d + z) * w + x;
|
||||||
|
|||||||
17
src/content/ContentPack.cpp
Normal file
17
src/content/ContentPack.cpp
Normal 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
src/content/ContentPack.h
Normal file
18
src/content/ContentPack.h
Normal 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_
|
||||||
@ -3,6 +3,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <vector>
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#define GLEW_STATIC
|
#define GLEW_STATIC
|
||||||
@ -27,6 +28,9 @@
|
|||||||
#include "files/files.h"
|
#include "files/files.h"
|
||||||
#include "files/engine_paths.h"
|
#include "files/engine_paths.h"
|
||||||
|
|
||||||
|
#include "content/ContentPack.h"
|
||||||
|
#include "frontend/locale/langs.h"
|
||||||
|
|
||||||
using std::unique_ptr;
|
using std::unique_ptr;
|
||||||
using std::shared_ptr;
|
using std::shared_ptr;
|
||||||
using std::string;
|
using std::string;
|
||||||
@ -55,6 +59,16 @@ Engine::Engine(EngineSettings& settings, EnginePaths* paths, Content* content)
|
|||||||
}
|
}
|
||||||
Audio::initialize();
|
Audio::initialize();
|
||||||
gui = new GUI();
|
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;
|
std::cout << "-- initializing finished" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -39,6 +39,9 @@ toml::Wrapper create_wrapper(EngineSettings& settings) {
|
|||||||
debug.add("generator-test-mode", &settings.debug.generatorTestMode);
|
debug.add("generator-test-mode", &settings.debug.generatorTestMode);
|
||||||
debug.add("show-chunk-borders", &settings.debug.showChunkBorders);
|
debug.add("show-chunk-borders", &settings.debug.showChunkBorders);
|
||||||
debug.add("do-write-lights", &settings.debug.doWriteLights);
|
debug.add("do-write-lights", &settings.debug.doWriteLights);
|
||||||
|
|
||||||
|
toml::Section& ui = wrapper.add("ui");
|
||||||
|
ui.add("language", &settings.ui.language);
|
||||||
return wrapper;
|
return wrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
#include "../locale/langs.h"
|
||||||
|
|
||||||
using namespace gui;
|
using namespace gui;
|
||||||
using glm::vec2;
|
using glm::vec2;
|
||||||
using glm::vec4;
|
using glm::vec4;
|
||||||
@ -11,7 +13,7 @@ using std::string;
|
|||||||
using std::wstring;
|
using std::wstring;
|
||||||
|
|
||||||
Button* guiutil::backButton(PagesControl* menu) {
|
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();
|
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* panel = new Panel(vec2(500, 200), vec4(8.0f), 8.0f);
|
||||||
panel->color(vec4(0.0f, 0.0f, 0.0f, 0.5f));
|
panel->color(vec4(0.0f, 0.0f, 0.0f, 0.5f));
|
||||||
panel->add(new Label(text));
|
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)
|
if (on_hidden)
|
||||||
on_hidden();
|
on_hidden();
|
||||||
menu->back();
|
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,
|
void guiutil::confirm(GUI* gui, wstring text, gui::runnable on_confirm,
|
||||||
wstring yestext, wstring notext) {
|
wstring yestext, wstring notext) {
|
||||||
|
if (yestext.empty()) yestext = langs::get(L"Yes");
|
||||||
|
if (notext.empty()) notext = langs::get(L"No");
|
||||||
|
|
||||||
PagesControl* menu = gui->getMenu();
|
PagesControl* menu = gui->getMenu();
|
||||||
Panel* panel = new Panel(vec2(600, 200), vec4(8.0f), 8.0f);
|
Panel* panel = new Panel(vec2(600, 200), vec4(8.0f), 8.0f);
|
||||||
panel->color(vec4(0.0f, 0.0f, 0.0f, 0.5f));
|
panel->color(vec4(0.0f, 0.0f, 0.0f, 0.5f));
|
||||||
|
|||||||
@ -13,7 +13,7 @@ namespace guiutil {
|
|||||||
gui::Button* gotoButton(std::wstring text, std::string page, gui::PagesControl* menu);
|
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 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,
|
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_
|
#endif // FRONTEND_GUI_GUI_UTIL_H_
|
||||||
|
|||||||
133
src/frontend/locale/langs.cpp
Normal file
133
src/frontend/locale/langs.cpp
Normal 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
src/frontend/locale/langs.h
Normal file
65
src/frontend/locale/langs.h
Normal 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
|
||||||
@ -23,6 +23,7 @@
|
|||||||
#include "../content/ContentLUT.h"
|
#include "../content/ContentLUT.h"
|
||||||
|
|
||||||
#include "gui/gui_util.h"
|
#include "gui/gui_util.h"
|
||||||
|
#include "locale/langs.h"
|
||||||
|
|
||||||
using glm::vec2;
|
using glm::vec2;
|
||||||
using glm::vec4;
|
using glm::vec4;
|
||||||
@ -41,7 +42,7 @@ void show_content_missing(GUI* gui, const Content* content, ContentLUT* lut) {
|
|||||||
PagesControl* menu = gui->getMenu();
|
PagesControl* menu = gui->getMenu();
|
||||||
Panel* panel = new Panel(vec2(500, 200), vec4(8.0f), 8.0f);
|
Panel* panel = new Panel(vec2(500, 200), vec4(8.0f), 8.0f);
|
||||||
panel->color(vec4(0.0f, 0.0f, 0.0f, 0.5f));
|
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));
|
Panel* subpanel = new Panel(vec2(500, 100));
|
||||||
subpanel->color(vec4(0.0f, 0.0f, 0.0f, 0.5f));
|
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);
|
subpanel->maxLength(400);
|
||||||
panel->add(subpanel);
|
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();
|
menu->back();
|
||||||
}));
|
}));
|
||||||
panel->refresh();
|
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* panel = new Panel(vec2(400, 200), vec4(5.0f), 1.0f);
|
||||||
panel->color(vec4(0.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));
|
Panel* worldsPanel = new Panel(vec2(390, 200), vec4(5.0f));
|
||||||
worldsPanel->color(vec4(1.0f, 1.0f, 1.0f, 0.07f));
|
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(worldsPanel);
|
||||||
panel->add(guiutil::gotoButton(L"Settings", "settings", menu));
|
panel->add(guiutil::gotoButton(langs::get(L"menu.settings"), "settings", menu));
|
||||||
panel->add((new Button(L"Quit", vec4(10.f)))->listenAction([](GUI* gui) {
|
panel->add((new Button(langs::get(L"menu.quit"), vec4(10.f)))->listenAction([](GUI* gui) {
|
||||||
Window::setShouldClose(true);
|
Window::setShouldClose(true);
|
||||||
}));
|
}));
|
||||||
panel->refresh();
|
panel->refresh();
|
||||||
@ -149,7 +150,7 @@ Panel* create_new_world_panel(Engine* engine, PagesControl* menu) {
|
|||||||
|
|
||||||
TextBox* worldNameInput;
|
TextBox* worldNameInput;
|
||||||
{
|
{
|
||||||
Label* label = new Label(L"World Name");
|
Label* label = new Label(langs::get(L"world.name"));
|
||||||
panel->add(label);
|
panel->add(label);
|
||||||
|
|
||||||
TextBox* input = new TextBox(L"New World", vec4(6.0f));
|
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;
|
TextBox* seedInput;
|
||||||
{
|
{
|
||||||
Label* label = new Label(L"Seed");
|
Label* label = new Label(langs::get(L"world.seed"));
|
||||||
panel->add(shared_ptr<UINode>(label));
|
panel->add(shared_ptr<UINode>(label));
|
||||||
|
|
||||||
uint64_t randseed = rand() ^ (rand() << 8) ^
|
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));
|
button->margin(vec4(1, 20, 1, 1));
|
||||||
vec4 basecolor = worldNameInput->color();
|
vec4 basecolor = worldNameInput->color();
|
||||||
button->listenAction([=](GUI*) {
|
button->listenAction([=](GUI*) {
|
||||||
@ -239,7 +240,7 @@ Panel* create_controls_panel(Engine* engine, PagesControl* menu) {
|
|||||||
std::wstringstream ss;
|
std::wstringstream ss;
|
||||||
ss << std::fixed << std::setprecision(1);
|
ss << std::fixed << std::setprecision(1);
|
||||||
ss << engine->getSettings().camera.sensitivity;
|
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);
|
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);
|
InputBindBox* bindbox = new InputBindBox(entry.second);
|
||||||
subpanel->add(bindbox);
|
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));
|
label->margin(vec4(6.0f));
|
||||||
subpanel->add(label);
|
subpanel->add(label);
|
||||||
scrollPanel->add(subpanel);
|
scrollPanel->add(subpanel);
|
||||||
@ -282,7 +283,7 @@ Panel* create_settings_panel(Engine* engine, PagesControl* menu) {
|
|||||||
|
|
||||||
/* Load Distance setting track bar */{
|
/* Load Distance setting track bar */{
|
||||||
panel->add((new Label(L""))->textSupplier([=]() {
|
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);
|
std::to_wstring(engine->getSettings().chunks.loadDistance);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -298,7 +299,7 @@ Panel* create_settings_panel(Engine* engine, PagesControl* menu) {
|
|||||||
|
|
||||||
/* Load Speed setting track bar */{
|
/* Load Speed setting track bar */{
|
||||||
panel->add((new Label(L""))->textSupplier([=]() {
|
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);
|
std::to_wstring(engine->getSettings().chunks.loadSpeed);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -317,7 +318,7 @@ Panel* create_settings_panel(Engine* engine, PagesControl* menu) {
|
|||||||
std::wstringstream ss;
|
std::wstringstream ss;
|
||||||
ss << std::fixed << std::setprecision(1);
|
ss << std::fixed << std::setprecision(1);
|
||||||
ss << engine->getSettings().graphics.fogCurve;
|
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);
|
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 */{
|
/* Fov setting track bar */{
|
||||||
panel->add((new Label(L""))->textSupplier([=]() {
|
panel->add((new Label(L""))->textSupplier([=]() {
|
||||||
int fov = (int)engine->getSettings().camera.fov;
|
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);
|
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;
|
engine->getSettings().display.swapInterval = checked;
|
||||||
});
|
});
|
||||||
checkpanel->add(checkbox);
|
checkpanel->add(checkbox);
|
||||||
checkpanel->add(new Label(L"V-Sync"));
|
checkpanel->add(new Label(langs::get(L"display.vsync")));
|
||||||
|
|
||||||
panel->add(checkpanel);
|
panel->add(checkpanel);
|
||||||
}
|
}
|
||||||
@ -379,12 +380,12 @@ Panel* create_settings_panel(Engine* engine, PagesControl* menu) {
|
|||||||
engine->getSettings().graphics.backlight = checked;
|
engine->getSettings().graphics.backlight = checked;
|
||||||
});
|
});
|
||||||
checkpanel->add(checkbox);
|
checkpanel->add(checkbox);
|
||||||
checkpanel->add(new Label(L"Backlight"));
|
checkpanel->add(new Label(langs::get(L"graphics.backlight")));
|
||||||
|
|
||||||
panel->add(checkpanel);
|
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->add(guiutil::backButton(menu));
|
||||||
panel->refresh();
|
panel->refresh();
|
||||||
return panel;
|
return panel;
|
||||||
@ -394,15 +395,15 @@ Panel* create_pause_panel(Engine* engine, PagesControl* menu) {
|
|||||||
Panel* panel = new Panel(vec2(400, 200));
|
Panel* panel = new Panel(vec2(400, 200));
|
||||||
panel->color(vec4(0.0f));
|
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*){
|
button->listenAction([=](GUI*){
|
||||||
menu->reset();
|
menu->reset();
|
||||||
});
|
});
|
||||||
panel->add(shared_ptr<UINode>(button));
|
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*){
|
button->listenAction([engine](GUI*){
|
||||||
engine->setScreen(shared_ptr<Screen>(new MenuScreen(engine)));
|
engine->setScreen(shared_ptr<Screen>(new MenuScreen(engine)));
|
||||||
});
|
});
|
||||||
|
|||||||
@ -61,12 +61,17 @@ struct DebugSettings {
|
|||||||
bool doWriteLights = true;
|
bool doWriteLights = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct UiSettings {
|
||||||
|
std::string language = "auto";
|
||||||
|
};
|
||||||
|
|
||||||
struct EngineSettings {
|
struct EngineSettings {
|
||||||
DisplaySettings display;
|
DisplaySettings display;
|
||||||
ChunksSettings chunks;
|
ChunksSettings chunks;
|
||||||
CameraSettings camera;
|
CameraSettings camera;
|
||||||
GraphicsSettings graphics;
|
GraphicsSettings graphics;
|
||||||
DebugSettings debug;
|
DebugSettings debug;
|
||||||
|
UiSettings ui;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SRC_SETTINGS_H_
|
#endif // SRC_SETTINGS_H_
|
||||||
@ -20,6 +20,15 @@ path platform::get_controls_file() {
|
|||||||
return path(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
|
#ifdef WIN32
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@ namespace platform {
|
|||||||
extern void configure_encoding();
|
extern void configure_encoding();
|
||||||
extern std::filesystem::path get_settings_file();
|
extern std::filesystem::path get_settings_file();
|
||||||
extern std::filesystem::path get_controls_file();
|
extern std::filesystem::path get_controls_file();
|
||||||
|
extern std::string detect_locale();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // UTIL_PLATFORM_H_
|
#endif // UTIL_PLATFORM_H_
|
||||||
Loading…
x
Reference in New Issue
Block a user