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

View 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

View 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
View 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
View File

@ -0,0 +1,11 @@
{
"langs": {
"en_US": {
"name": "English"
},
"ru_RU": {
"name": "Русский"
}
},
"fallback": "en_US"
}

45
res/texts/ru_RU.txt Normal file
View 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=Сменить Режим Камеры

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) {

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);

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();
}

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;

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
src/content/ContentPack.h Normal file
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_

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;
}

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;
}

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));

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_

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);
}

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

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)));
});

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_

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>

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_