多言語対応
守矢諏訪子 <suwako@076.moe>、2026-06-27 00:20
コミット bab9c874cb2da716bc0e91aa5e87644ece1d2b5d
・親 ac8090d
・ファイルを見る
13個のファイル、+442 -78
| @@ -38,3 +38,4 @@ | ||
| 38 | 38 | |
| 39 | 39 | #include <database/setup.hh> |
| 40 | 40 | #include <database/shaderdata.hh> |
| 41 | +#include <database/locale.hh> |
| @@ -0,0 +1,73 @@ | ||
| 1 | +/* | |
| 2 | +# 076 License | |
| 3 | + | |
| 4 | +Copyright (c) テクニカル諏訪子 | |
| 5 | + | |
| 6 | +Permission is hereby granted to any person obtaining a copy of the software | |
| 7 | +Shader Playground (the "Software") to use, modify, merge, copy, publish, distribute, | |
| 8 | +sublicense, and/or sell copies of the Software, subject to the following conditions: | |
| 9 | + | |
| 10 | + 1. **Origin Attribution**: | |
| 11 | + - You must not misrepresent the origin of the Software; you must not claim | |
| 12 | + you created the original Software. | |
| 13 | + - If the Software is used in a product, you must either: | |
| 14 | + a. Provide clear attribution in the product's documentation, user interface, | |
| 15 | + or other visible areas, **OR** | |
| 16 | + b. Pay the original developers a fee they specify in writing. | |
| 17 | + 2. **Usage Restriction**: | |
| 18 | + - The Software, or any derivative works, dependencies, or libraries | |
| 19 | + incorporating it, must not be used for censorship or to suppress freedom of | |
| 20 | + speech, expression, or creativity. Prohibited uses include, but are not | |
| 21 | + limited to: | |
| 22 | + - Censorship of so-called "hate speech", visuals, non-mainstream opinions, | |
| 23 | + ideas, or objective reality. | |
| 24 | + - Tools or systems designed to restrict access to information or | |
| 25 | + artistic works. | |
| 26 | + 3. **Notice Preservation**: | |
| 27 | + - This license and the above copyright notice must remain intact in all copies | |
| 28 | + of the source code. | |
| 29 | + | |
| 30 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, | |
| 31 | +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A | |
| 32 | +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | |
| 33 | +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF | |
| 34 | +CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE | |
| 35 | +OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 36 | +*/ | |
| 37 | +#pragma once | |
| 38 | + | |
| 39 | +#include <util/types.hh> | |
| 40 | +#include <unordered_map> | |
| 41 | + | |
| 42 | +namespace sqlitepp { | |
| 43 | + class Instance; | |
| 44 | +} // namespace sqlitepp | |
| 45 | + | |
| 46 | +namespace db { | |
| 47 | + struct Languages { | |
| 48 | + i32 id; | |
| 49 | + string name; | |
| 50 | + string code; | |
| 51 | + std::unordered_map<string, string> dict; | |
| 52 | + }; // struct Languages | |
| 53 | + | |
| 54 | + class Locale { | |
| 55 | + public: | |
| 56 | + Locale(sqlitepp::Instance *db); | |
| 57 | + ~Locale() {} | |
| 58 | + | |
| 59 | + string GetWord(const string &key) const; | |
| 60 | + void SetLanguage(i32 id); | |
| 61 | + | |
| 62 | + vector<Languages> GetAllLanguages() const { return m_Langs; } | |
| 63 | + Languages GetCurrentLanguage() const { return m_CurLang; } | |
| 64 | + | |
| 65 | + private: | |
| 66 | + void setLang() noexcept; | |
| 67 | + | |
| 68 | + private: | |
| 69 | + sqlitepp::Instance *m_DB; | |
| 70 | + vector<Languages> m_Langs; | |
| 71 | + Languages m_CurLang; | |
| 72 | + }; // class Locale | |
| 73 | +} // namespace db |
| @@ -37,6 +37,7 @@ | ||
| 37 | 37 | #pragma once |
| 38 | 38 | |
| 39 | 39 | #include <util/types.hh> |
| 40 | +#include <util/structs.hh> | |
| 40 | 41 | |
| 41 | 42 | namespace gui { |
| 42 | 43 | enum class LogType { |
| @@ -54,7 +55,7 @@ | ||
| 54 | 55 | class ConsoleLog { |
| 55 | 56 | public: |
| 56 | 57 | void Add(LogEntry entry); |
| 57 | - void Draw(cstr title = "コンソール"); | |
| 58 | + void Draw(GlfwInfo &info, cstr title = "コンソール"); | |
| 58 | 59 | void Clear(); |
| 59 | 60 | |
| 60 | 61 | private: |
| @@ -58,6 +58,8 @@ | ||
| 58 | 58 | void SetCode(string code); |
| 59 | 59 | void RenderStatusBar(TextEditor editor); |
| 60 | 60 | |
| 61 | + void SetTitle(const string &title) { m_Title = title; } | |
| 62 | + | |
| 61 | 63 | private: |
| 62 | 64 | TextEditor m_Editor; |
| 63 | 65 | ImFont *m_CjkFont; |
| @@ -37,6 +37,7 @@ | ||
| 37 | 37 | #pragma once |
| 38 | 38 | |
| 39 | 39 | #include <util/glfwpp.hh> |
| 40 | +#include <database/locale.hh> | |
| 40 | 41 | #include <version.hh> |
| 41 | 42 | #include <functional> |
| 42 | 43 | #include <string> |
| @@ -49,8 +50,11 @@ | ||
| 49 | 50 | bool isAbout = false; |
| 50 | 51 | bool isManual = false; |
| 51 | 52 | bool isSettings = false; |
| 53 | + bool isLangChange = false; | |
| 52 | 54 | |
| 53 | 55 | std::string version = version::full; |
| 56 | + | |
| 57 | + db::Locale *i18n; | |
| 54 | 58 | |
| 55 | 59 | std::function<void()> compile; |
| 56 | 60 | std::function<void()> save; |
変更
main.cc
| @@ -102,6 +102,10 @@ | ||
| 102 | 102 | return -1; |
| 103 | 103 | } |
| 104 | 104 | |
| 105 | + // 言語 | |
| 106 | + db::Locale i18n(&db); | |
| 107 | + glfwInfo.i18n = &i18n; | |
| 108 | + | |
| 105 | 109 | // コンソール |
| 106 | 110 | gui::ConsoleLog cmd; |
| 107 | 111 | |
| @@ -128,13 +132,13 @@ | ||
| 128 | 132 | LUA = c.luaCode; |
| 129 | 133 | } |
| 130 | 134 | |
| 131 | - string title = shaderName + "(頂点シェーダー)"; | |
| 135 | + string title = shaderName + "(" + glfwInfo.i18n->GetWord("editorvertshader") + ")"; | |
| 132 | 136 | gui::Editor vertEditor((title + "###VertexEditor").c_str(), "VertexEditor", VERT.code, gui::Glsl, ge.GetCjkFont(), ge.GetMonoFont()); |
| 133 | 137 | |
| 134 | - title = shaderName + "(フラグメントシェーダー)"; | |
| 138 | + title = shaderName + "(" + glfwInfo.i18n->GetWord("editorfragshader") + ")"; | |
| 135 | 139 | gui::Editor fragEditor((title + "###FragmentEditor").c_str(), "FragmentEditor", FRAG.code, gui::Glsl, ge.GetCjkFont(), ge.GetMonoFont()); |
| 136 | 140 | |
| 137 | - title = shaderName + "(Luaエディター)"; | |
| 141 | + title = shaderName + "(" + glfwInfo.i18n->GetWord("editorlua") + ")"; | |
| 138 | 142 | gui::Editor luaEditor((title + "###LuaEditor").c_str(), "LuaEditor", LUA.code, gui::Lua, ge.GetCjkFont(), ge.GetMonoFont()); |
| 139 | 143 | |
| 140 | 144 | // シェーダーをコンパイル |
| @@ -179,9 +183,9 @@ | ||
| 179 | 183 | if (!luaEngine.Validate(LUA.code)) { |
| 180 | 184 | gui::LogEntry entry; |
| 181 | 185 | entry.type = gui::LogType::Error; |
| 182 | - entry.text = "Luaコードが不正ですので、コンパイル出来ませんでした。"; | |
| 186 | + entry.text = glfwInfo.i18n->GetWord("consolelogerrorluainvalidcomp"); | |
| 183 | 187 | cmd.Add(entry); |
| 184 | - std::cout << entry.text << std::endl; | |
| 188 | + std::cerr << entry.text << std::endl; | |
| 185 | 189 | return; |
| 186 | 190 | } |
| 187 | 191 | } catch (const std::exception &e) { |
| @@ -189,7 +193,11 @@ | ||
| 189 | 193 | entry.type = gui::LogType::Error; |
| 190 | 194 | entry.text = e.what(); |
| 191 | 195 | cmd.Add(entry); |
| 192 | - std::cout << entry.text << std::endl; | |
| 196 | + std::cerr << entry.text << std::endl; | |
| 197 | + entry.text = glfwInfo.i18n->GetWord("consolelogerrorluainvalidcomp"); | |
| 198 | + cmd.Add(entry); | |
| 199 | + std::cerr << entry.text << std::endl; | |
| 200 | + return; | |
| 193 | 201 | } |
| 194 | 202 | |
| 195 | 203 | shaderProgram.Reload(VERT.code, FRAG.code); |
| @@ -224,19 +232,22 @@ | ||
| 224 | 232 | glEnableVertexAttribArray(a.location); |
| 225 | 233 | } |
| 226 | 234 | } |
| 235 | + | |
| 236 | + gui::LogEntry entry; | |
| 237 | + entry.type = gui::LogType::Info; | |
| 238 | + entry.text = glfwInfo.i18n->GetWord("consoleloginfocompileok"); | |
| 239 | + cmd.Add(entry); | |
| 240 | + std::cout << entry.text << std::endl; | |
| 227 | 241 | } catch (const std::exception &e) { |
| 228 | 242 | gui::LogEntry entry; |
| 229 | 243 | entry.type = gui::LogType::Error; |
| 230 | 244 | entry.text = e.what(); |
| 231 | 245 | cmd.Add(entry); |
| 232 | 246 | std::cerr << entry.text << std::endl; |
| 247 | + entry.text = glfwInfo.i18n->GetWord("consolelogerrorluainvalidcomp"); | |
| 248 | + cmd.Add(entry); | |
| 249 | + std::cerr << entry.text << std::endl; | |
| 233 | 250 | } |
| 234 | - | |
| 235 | - gui::LogEntry entry; | |
| 236 | - entry.type = gui::LogType::Info; | |
| 237 | - entry.text = "シェーダーをコンパイルしました。"; | |
| 238 | - cmd.Add(entry); | |
| 239 | - std::cout << entry.text << std::endl; | |
| 240 | 251 | }; |
| 241 | 252 | |
| 242 | 253 | glfwInfo.save = [&]() { |
| @@ -245,31 +256,46 @@ | ||
| 245 | 256 | LUA.code = luaEditor.Get().GetText(); |
| 246 | 257 | |
| 247 | 258 | try { |
| 248 | - if (!luaEngine.Validate(LUA.code)) { | |
| 259 | + try { | |
| 260 | + if (!luaEngine.Validate(LUA.code)) { | |
| 261 | + gui::LogEntry entry; | |
| 262 | + entry.type = gui::LogType::Error; | |
| 263 | + entry.text = glfwInfo.i18n->GetWord("consolelogerrorluainvalidsave"); | |
| 264 | + cmd.Add(entry); | |
| 265 | + std::cout << entry.text << std::endl; | |
| 266 | + return; | |
| 267 | + } | |
| 268 | + } catch (const std::exception &e) { | |
| 249 | 269 | gui::LogEntry entry; |
| 250 | 270 | entry.type = gui::LogType::Error; |
| 251 | - entry.text = "Luaコードが不正ですので、保存出来ませんでした。"; | |
| 271 | + entry.text = e.what(); | |
| 252 | 272 | cmd.Add(entry); |
| 253 | 273 | std::cout << entry.text << std::endl; |
| 274 | + entry.text = glfwInfo.i18n->GetWord("consolelogerrorluainvalidsave"); | |
| 275 | + cmd.Add(entry); | |
| 276 | + std::cout << entry.text << std::endl; | |
| 254 | 277 | return; |
| 255 | 278 | } |
| 279 | + | |
| 280 | + db::SaveCode(db, VERT); | |
| 281 | + db::SaveCode(db, FRAG); | |
| 282 | + db::SaveCode(db, LUA); | |
| 283 | + | |
| 284 | + gui::LogEntry entry; | |
| 285 | + entry.type = gui::LogType::Info; | |
| 286 | + entry.text = glfwInfo.i18n->GetWord("consoleloginfosaveok"); | |
| 287 | + cmd.Add(entry); | |
| 288 | + std::cout << entry.text << std::endl; | |
| 256 | 289 | } catch (const std::exception &e) { |
| 257 | 290 | gui::LogEntry entry; |
| 258 | 291 | entry.type = gui::LogType::Error; |
| 259 | 292 | entry.text = e.what(); |
| 260 | 293 | cmd.Add(entry); |
| 261 | 294 | std::cout << entry.text << std::endl; |
| 295 | + entry.text = glfwInfo.i18n->GetWord("consolelogerrorluainvalidsave"); | |
| 296 | + cmd.Add(entry); | |
| 297 | + std::cout << entry.text << std::endl; | |
| 262 | 298 | } |
| 263 | - | |
| 264 | - db::SaveCode(db, VERT); | |
| 265 | - db::SaveCode(db, FRAG); | |
| 266 | - db::SaveCode(db, LUA); | |
| 267 | - | |
| 268 | - gui::LogEntry entry; | |
| 269 | - entry.type = gui::LogType::Info; | |
| 270 | - entry.text = "シェーダー及びLuaファイルを保存しました。"; | |
| 271 | - cmd.Add(entry); | |
| 272 | - std::cout << entry.text << std::endl; | |
| 273 | 299 | }; |
| 274 | 300 | |
| 275 | 301 | glfw.PollEvents(); |
| @@ -310,6 +336,16 @@ | ||
| 310 | 336 | |
| 311 | 337 | isHold = isSaveKey || isCompileKey || isAboutKey || isQuitKey || isManualKey || isSettingsKey; |
| 312 | 338 | |
| 339 | + if (glfwInfo.isLangChange) { | |
| 340 | + string nTit = shaderName + "(" + glfwInfo.i18n->GetWord("editorvertshader") + ")"; | |
| 341 | + vertEditor.SetTitle(nTit + "###VertexEditor"); | |
| 342 | + nTit = shaderName + "(" + glfwInfo.i18n->GetWord("editorfragshader") + ")"; | |
| 343 | + fragEditor.SetTitle(nTit + "###FragmentEditor"); | |
| 344 | + nTit = shaderName + "(" + glfwInfo.i18n->GetWord("editorlua") + ")"; | |
| 345 | + luaEditor.SetTitle(nTit + "###LuaEditor"); | |
| 346 | + glfwInfo.isLangChange = false; | |
| 347 | + } | |
| 348 | + | |
| 313 | 349 | if (window.GetAttrib(glfwpp::Attributes::Iconified) != 0) { |
| 314 | 350 | ImGui_ImplGlfw_Sleep(10); |
| 315 | 351 | continue; |
| @@ -324,7 +360,7 @@ | ||
| 324 | 360 | vertEditor.Render(); |
| 325 | 361 | fragEditor.Render(); |
| 326 | 362 | luaEditor.Render(); |
| 327 | - cmd.Draw(); | |
| 363 | + cmd.Draw(glfwInfo, glfwInfo.i18n->GetWord("editorconsole").c_str()); | |
| 328 | 364 | about.Draw(glfwInfo); |
| 329 | 365 | manual.Draw(glfwInfo); |
| 330 | 366 |
| @@ -0,0 +1,103 @@ | ||
| 1 | +/* | |
| 2 | +# 076 License | |
| 3 | + | |
| 4 | +Copyright (c) テクニカル諏訪子 | |
| 5 | + | |
| 6 | +Permission is hereby granted to any person obtaining a copy of the software | |
| 7 | +Shader Playground (the "Software") to use, modify, merge, copy, publish, distribute, | |
| 8 | +sublicense, and/or sell copies of the Software, subject to the following conditions: | |
| 9 | + | |
| 10 | + 1. **Origin Attribution**: | |
| 11 | + - You must not misrepresent the origin of the Software; you must not claim | |
| 12 | + you created the original Software. | |
| 13 | + - If the Software is used in a product, you must either: | |
| 14 | + a. Provide clear attribution in the product's documentation, user interface, | |
| 15 | + or other visible areas, **OR** | |
| 16 | + b. Pay the original developers a fee they specify in writing. | |
| 17 | + 2. **Usage Restriction**: | |
| 18 | + - The Software, or any derivative works, dependencies, or libraries | |
| 19 | + incorporating it, must not be used for censorship or to suppress freedom of | |
| 20 | + speech, expression, or creativity. Prohibited uses include, but are not | |
| 21 | + limited to: | |
| 22 | + - Censorship of so-called "hate speech", visuals, non-mainstream opinions, | |
| 23 | + ideas, or objective reality. | |
| 24 | + - Tools or systems designed to restrict access to information or | |
| 25 | + artistic works. | |
| 26 | + 3. **Notice Preservation**: | |
| 27 | + - This license and the above copyright notice must remain intact in all copies | |
| 28 | + of the source code. | |
| 29 | + | |
| 30 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, | |
| 31 | +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A | |
| 32 | +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | |
| 33 | +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF | |
| 34 | +CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE | |
| 35 | +OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 36 | +*/ | |
| 37 | +#include <database/locale.hh> | |
| 38 | +#include <util/sqlitepp.hh> | |
| 39 | +#include <sqlite3.h> | |
| 40 | +#include <iostream> | |
| 41 | + | |
| 42 | +namespace db { | |
| 43 | + Locale::Locale(sqlitepp::Instance *db) : m_DB(db) { | |
| 44 | + { | |
| 45 | + sqlitepp::Stmt stmt(db->GetDB()); | |
| 46 | + stmt.Prepare("SELECT id, name, code FROM languages ORDER BY id ASC;"); | |
| 47 | + | |
| 48 | + while (stmt.Step() == SQLITE_ROW) { | |
| 49 | + Languages lang; | |
| 50 | + lang.id = stmt.ColumnInt(0); | |
| 51 | + lang.name = stmt.ColumnText(1); | |
| 52 | + lang.code = stmt.ColumnText(2); | |
| 53 | + | |
| 54 | + sqlitepp::Stmt langStmt(db->GetDB()); | |
| 55 | + langStmt.Prepare("SELECT key, value FROM locale WHERE language_id = ?;"); | |
| 56 | + langStmt.BindInt(1, lang.id); | |
| 57 | + | |
| 58 | + while (langStmt.Step() == SQLITE_ROW) | |
| 59 | + lang.dict.emplace(langStmt.ColumnText(0), langStmt.ColumnText(1)); | |
| 60 | + | |
| 61 | + m_Langs.emplace_back(std::move(lang)); | |
| 62 | + } | |
| 63 | + } | |
| 64 | + | |
| 65 | + setLang(); | |
| 66 | + } | |
| 67 | + | |
| 68 | + string Locale::GetWord(const string &key) const { | |
| 69 | + auto it = m_CurLang.dict.find(key); | |
| 70 | + return it != m_CurLang.dict.end() ? it->second : key; | |
| 71 | + } | |
| 72 | + | |
| 73 | + void Locale::SetLanguage(i32 id) { | |
| 74 | + sqlitepp::Stmt stmt(m_DB->GetDB()); | |
| 75 | + stmt.Prepare("UPDATE settings SET value = ? WHERE key = ?;"); | |
| 76 | + stmt.BindInt(1, id); | |
| 77 | + stmt.BindText(2, "locale"); | |
| 78 | + | |
| 79 | + if (stmt.Step() != SQLITE_DONE) { | |
| 80 | + std::cerr << "言語変更に失敗。" << std::endl; | |
| 81 | + return; | |
| 82 | + } | |
| 83 | + setLang(); | |
| 84 | + } | |
| 85 | + | |
| 86 | + ////////// | |
| 87 | + | |
| 88 | + void Locale::setLang() noexcept { | |
| 89 | + sqlitepp::Stmt stmt(m_DB->GetDB()); | |
| 90 | + stmt.Prepare("SELECT value FROM settings WHERE key = ?;"); | |
| 91 | + stmt.BindText(1, "locale"); | |
| 92 | + | |
| 93 | + if ((stmt.Step() == SQLITE_ROW)) { | |
| 94 | + i32 curLangId = stmt.ColumnInt(0); | |
| 95 | + for (const auto &lang : m_Langs) { | |
| 96 | + if (lang.id == curLangId) { | |
| 97 | + m_CurLang = lang; | |
| 98 | + break; | |
| 99 | + } | |
| 100 | + } | |
| 101 | + } | |
| 102 | + } | |
| 103 | +} // namespace db |
| @@ -43,6 +43,31 @@ | ||
| 43 | 43 | #include <iostream> |
| 44 | 44 | |
| 45 | 45 | namespace db { |
| 46 | + void setDefaultSetting(sqlitepp::Instance &db, const string &key, i32 value) { | |
| 47 | + sqlitepp::Stmt stmt(db.GetDB()); | |
| 48 | + stmt.Prepare("INSERT INTO settings (key, value) VALUES (?, ?);"); | |
| 49 | + stmt.BindText(1, key); | |
| 50 | + stmt.BindInt(2, value); | |
| 51 | + assert(stmt.Step() == SQLITE_DONE && "最初のシェーダーを入れ込みに失敗。\n"); | |
| 52 | + } | |
| 53 | + | |
| 54 | + void createLanguage(sqlitepp::Instance &db, const string &name, const string &code) { | |
| 55 | + sqlitepp::Stmt stmt(db.GetDB()); | |
| 56 | + stmt.Prepare("INSERT INTO languages (name, code) VALUES (?, ?);"); | |
| 57 | + stmt.BindText(1, name); | |
| 58 | + stmt.BindText(2, code); | |
| 59 | + assert(stmt.Step() == SQLITE_DONE && "最初のシェーダーを入れ込みに失敗。\n"); | |
| 60 | + } | |
| 61 | + | |
| 62 | + void createWord(sqlitepp::Instance &db, i32 langId, const string &key, const string &value = "") { | |
| 63 | + sqlitepp::Stmt stmt(db.GetDB()); | |
| 64 | + stmt.Prepare("INSERT INTO locale (language_id, key, value) VALUES (?, ?, ?);"); | |
| 65 | + stmt.BindInt(1, langId); | |
| 66 | + stmt.BindText(2, key); | |
| 67 | + stmt.BindText(3, value); | |
| 68 | + assert(stmt.Step() == SQLITE_DONE && "最初のシェーダーを入れ込みに失敗。\n"); | |
| 69 | + } | |
| 70 | + | |
| 46 | 71 | void Initialize(sqlitepp::Instance &db) { |
| 47 | 72 | cstr sql = R"(CREATE TABLE IF NOT EXISTS version ( |
| 48 | 73 | id INTEGER PRIMARY KEY, |
| @@ -97,7 +122,28 @@ | ||
| 97 | 122 | UPDATE shader_code |
| 98 | 123 | SET updated_at = CURRENT_TIMESTAMP |
| 99 | 124 | WHERE id = NEW.id; |
| 100 | -END;)"; | |
| 125 | +END; | |
| 126 | + | |
| 127 | +CREATE TABLE IF NOT EXISTS languages ( | |
| 128 | + id INTEGER PRIMARY KEY, | |
| 129 | + name TEXT NOT NULL, | |
| 130 | + code TEXT NOT NULL | |
| 131 | +); | |
| 132 | + | |
| 133 | +CREATE TABLE IF NOT EXISTS locale ( | |
| 134 | + id INTEGER PRIMARY KEY, | |
| 135 | + language_id INTEGER, | |
| 136 | + key TEXT NOT NULL, | |
| 137 | + value TEXT, | |
| 138 | + | |
| 139 | + FOREIGN KEY (language_id) REFERENCES languages(id) ON DELETE CASCADE | |
| 140 | +); | |
| 141 | + | |
| 142 | +CREATE TABLE IF NOT EXISTS settings ( | |
| 143 | + id INTEGER PRIMARY KEY, | |
| 144 | + key TEXT NOT NULL, | |
| 145 | + value INTEGER | |
| 146 | +);)"; | |
| 101 | 147 | |
| 102 | 148 | db.Exec(sql); |
| 103 | 149 | |
| @@ -118,6 +164,86 @@ | ||
| 118 | 164 | stmt.BindInt(3, version::revision); |
| 119 | 165 | |
| 120 | 166 | assert(stmt.Step() == SQLITE_DONE && "最初のシェーダーを入れ込みに失敗。\n"); |
| 167 | + } | |
| 168 | + | |
| 169 | + { | |
| 170 | + setDefaultSetting(db, "locale", 1); | |
| 171 | + } | |
| 172 | + | |
| 173 | + { | |
| 174 | + createLanguage(db, "日本語", "ja_JP"); | |
| 175 | + createLanguage(db, "English", "en_US"); | |
| 176 | + } | |
| 177 | + | |
| 178 | + { | |
| 179 | + createWord(db, 1, "file", "ファイル"); | |
| 180 | + createWord(db, 2, "file", "File"); | |
| 181 | + createWord(db, 1, "filenew", "新しいファイル"); | |
| 182 | + createWord(db, 2, "filenew", "New File"); | |
| 183 | + createWord(db, 1, "fileopen", "開く"); | |
| 184 | + createWord(db, 2, "fileopen", "Open"); | |
| 185 | + createWord(db, 1, "filesave", "保存"); | |
| 186 | + createWord(db, 2, "filesave", "Save"); | |
| 187 | + createWord(db, 1, "quit", "終了"); | |
| 188 | + createWord(db, 2, "quit", "Quit"); | |
| 189 | + createWord(db, 1, "editorvertshader", "頂点シェーダー"); | |
| 190 | + createWord(db, 2, "editorvertshader", "Vertex Shader"); | |
| 191 | + createWord(db, 1, "editorfragshader", "フラグメントシェーダー"); | |
| 192 | + createWord(db, 2, "editorfragshader", "Fragment Shader"); | |
| 193 | + createWord(db, 1, "editorlua", "Luaエディター"); | |
| 194 | + createWord(db, 2, "editorlua", "Lua Editor"); | |
| 195 | + createWord(db, 1, "editorviewport", "ビューポート"); | |
| 196 | + createWord(db, 2, "editorviewport", "Viewport"); | |
| 197 | + createWord(db, 1, "editorconsole", "コンソール"); | |
| 198 | + createWord(db, 2, "editorconsole", "Console"); | |
| 199 | + createWord(db, 1, "editorconsoleclear", "消す"); | |
| 200 | + createWord(db, 2, "editorconsoleclear", "Clear"); | |
| 201 | + createWord(db, 1, "edit", "編集"); | |
| 202 | + createWord(db, 2, "edit", "Edit"); | |
| 203 | + createWord(db, 1, "editundo", "元に戻す"); | |
| 204 | + createWord(db, 2, "editundo", "Undo"); | |
| 205 | + createWord(db, 1, "editredo", "やり直し"); | |
| 206 | + createWord(db, 2, "editredo", "Redo"); | |
| 207 | + createWord(db, 1, "editcut", "切り取り"); | |
| 208 | + createWord(db, 2, "editcut", "Cut"); | |
| 209 | + createWord(db, 1, "editcopy", "コピー"); | |
| 210 | + createWord(db, 2, "editcopy", "Copy"); | |
| 211 | + createWord(db, 1, "editpaste", "貼り付け"); | |
| 212 | + createWord(db, 2, "editpaste", "Paste"); | |
| 213 | + createWord(db, 1, "editduplicate", "行を複製"); | |
| 214 | + createWord(db, 2, "editduplicate", "Duplicate"); | |
| 215 | + createWord(db, 1, "settings", "設定"); | |
| 216 | + createWord(db, 2, "settings", "Settings"); | |
| 217 | + createWord(db, 1, "build", "ビルド"); | |
| 218 | + createWord(db, 2, "build", "Build"); | |
| 219 | + createWord(db, 1, "buildcompile", "コンパイル"); | |
| 220 | + createWord(db, 2, "buildcompile", "Compile"); | |
| 221 | + createWord(db, 1, "help", "ヘルプ"); | |
| 222 | + createWord(db, 2, "help", "Help"); | |
| 223 | + createWord(db, 1, "helpmanual", "マニュアル"); | |
| 224 | + createWord(db, 2, "helpmanual", "Manual"); | |
| 225 | + createWord(db, 1, "helpmanualoverview", "概要"); | |
| 226 | + createWord(db, 2, "helpmanualoverview", "Overview"); | |
| 227 | + createWord(db, 1, "helpmanualshader", "シェーダー"); | |
| 228 | + createWord(db, 2, "helpmanualshader", "Shader"); | |
| 229 | + createWord(db, 1, "helpmanuallua", "Luaエンジン"); | |
| 230 | + createWord(db, 2, "helpmanuallua", "Lua Engine"); | |
| 231 | + createWord(db, 1, "helpmanualhotkey", "ホットキー"); | |
| 232 | + createWord(db, 2, "helpmanualhotkey", "Hotkeys"); | |
| 233 | + createWord(db, 1, "helpabout", "Shader Playgroundについて"); | |
| 234 | + createWord(db, 2, "helpabout", "About Shader Playground"); | |
| 235 | + createWord(db, 1, "currentfps", "現在フレームレート"); | |
| 236 | + createWord(db, 2, "currentfps", "Current Frame Rate"); | |
| 237 | + createWord(db, 1, "dateformat", "%Y年%m月%d日 %H:%M:%S"); | |
| 238 | + createWord(db, 2, "dateformat", "%Y/%m/%d %H:%M:%S"); | |
| 239 | + createWord(db, 1, "consolelogerrorluainvalidcomp", "Luaコードが不正ですので、コンパイル出来ませんでした。"); | |
| 240 | + createWord(db, 2, "consolelogerrorluainvalidcomp", "Your Lua code is invalid, so it could not be compiled."); | |
| 241 | + createWord(db, 1, "consolelogerrorluainvalidsave", "Luaコードが不正ですので、保存出来ませんでした。"); | |
| 242 | + createWord(db, 2, "consolelogerrorluainvalidsave", "Your Lua code is invalid, so it could not be saved."); | |
| 243 | + createWord(db, 1, "consoleloginfocompileok", "シェーダーをコンパイルしました。"); | |
| 244 | + createWord(db, 2, "consoleloginfocompileok", "Shaders got compiled successfully."); | |
| 245 | + createWord(db, 1, "consoleloginfosaveok", "シェーダー及びLuaを保存しました。"); | |
| 246 | + createWord(db, 2, "consoleloginfosaveok", "Shaders and Lua have been saved."); | |
| 121 | 247 | } |
| 122 | 248 | |
| 123 | 249 | i64 shaderId = 0; |
| @@ -43,10 +43,17 @@ | ||
| 43 | 43 | |
| 44 | 44 | ImGuiWindowFlags flags = 0; |
| 45 | 45 | ImGui::SetNextWindowSize({400, 300}, ImGuiCond_Appearing); |
| 46 | - ImGui::Begin("Shader Playgroundについて", &glfw.isAbout, flags); | |
| 46 | + ImGui::Begin(glfw.i18n->GetWord("helpabout").c_str(), &glfw.isAbout, flags); | |
| 47 | 47 | string name = "Shader Playground " + glfw.version; |
| 48 | - string copyright = "© 2026 - 076スタジオ合同会社"; | |
| 49 | - string developer = "開発者:テクニカル諏訪子"; | |
| 48 | + string copyright; | |
| 49 | + string developer; | |
| 50 | + if (glfw.i18n->GetCurrentLanguage().code == "ja_JP") { | |
| 51 | + copyright = "© 2026 - 076スタジオ合同会社"; | |
| 52 | + developer = "開発者:テクニカル諏訪子"; | |
| 53 | + } else { | |
| 54 | + copyright = "© 2026 - 076 Studio LLC"; | |
| 55 | + developer = "Developer: TechnicalSuwako"; | |
| 56 | + } | |
| 50 | 57 | |
| 51 | 58 | ImGui::SetWindowFontScale(1.8f); |
| 52 | 59 | ImGui::Text(name.c_str()); |
| @@ -42,9 +42,9 @@ | ||
| 42 | 42 | m_Lines.push_back(entry); |
| 43 | 43 | } |
| 44 | 44 | |
| 45 | - void ConsoleLog::Draw(cstr title) { | |
| 45 | + void ConsoleLog::Draw(GlfwInfo &info, cstr title) { | |
| 46 | 46 | ImGui::Begin((string(title) + "###Console").c_str()); |
| 47 | - if (ImGui::Button("消す")) Clear(); | |
| 47 | + if (ImGui::Button(info.i18n->GetWord("editorconsoleclear").c_str())) Clear(); | |
| 48 | 48 | ImGui::Separator(); |
| 49 | 49 | for (const auto &line : m_Lines) { |
| 50 | 50 | if (line.type == LogType::Error) |
| @@ -45,25 +45,25 @@ | ||
| 45 | 45 | ImGuiWindowFlags flags = ImGuiWindowFlags_NoCollapse; |
| 46 | 46 | |
| 47 | 47 | ImGui::SetNextWindowSize({ 600, 700 }, ImGuiCond_Appearing); |
| 48 | - ImGui::Begin("マニュアル", &glfw.isManual, flags); | |
| 48 | + ImGui::Begin(glfw.i18n->GetWord("helpmanual").c_str(), &glfw.isManual, flags); | |
| 49 | 49 | |
| 50 | 50 | if (ImGui::BeginTabBar("ManualTabs")) { |
| 51 | - if (ImGui::BeginTabItem("概要")) { | |
| 51 | + if (ImGui::BeginTabItem(glfw.i18n->GetWord("helpmanualoverview").c_str())) { | |
| 52 | 52 | m_CurPage = "index"; |
| 53 | 53 | ImGui::EndTabItem(); |
| 54 | 54 | } |
| 55 | 55 | |
| 56 | - if (ImGui::BeginTabItem("シューだー")) { | |
| 56 | + if (ImGui::BeginTabItem(glfw.i18n->GetWord("helpmanualshader").c_str())) { | |
| 57 | 57 | m_CurPage = "shader"; |
| 58 | 58 | ImGui::EndTabItem(); |
| 59 | 59 | } |
| 60 | 60 | |
| 61 | - if (ImGui::BeginTabItem("Luaエンジン")) { | |
| 61 | + if (ImGui::BeginTabItem(glfw.i18n->GetWord("helpmanuallua").c_str())) { | |
| 62 | 62 | m_CurPage = "lua"; |
| 63 | 63 | ImGui::EndTabItem(); |
| 64 | 64 | } |
| 65 | 65 | |
| 66 | - if (ImGui::BeginTabItem("ホットキー")) { | |
| 66 | + if (ImGui::BeginTabItem(glfw.i18n->GetWord("helpmanualhotkey").c_str())) { | |
| 67 | 67 | m_CurPage = "hotkeys"; |
| 68 | 68 | ImGui::EndTabItem(); |
| 69 | 69 | } |
| @@ -76,40 +76,43 @@ | ||
| 76 | 76 | ImGui::BeginChild("ManualContent", ImVec2(0, 0), 0, ImGuiWindowFlags_HorizontalScrollbar); |
| 77 | 77 | |
| 78 | 78 | if (m_CurPage == "index") { |
| 79 | - ImGui::TextWrapped("Shader Playgroundへようこそ!"); | |
| 80 | - ImGui::Dummy(ImVec2(0, 10)); | |
| 81 | - renderMarkdown(R"(# 基本操作 | |
| 79 | + if (glfw.i18n->GetCurrentLanguage().code == "ja_JP") { | |
| 80 | + ImGui::TextWrapped("Shader Playgroundへようこそ!"); | |
| 81 | + ImGui::Dummy(ImVec2(0, 10)); | |
| 82 | + renderMarkdown(R"(# 基本操作 | |
| 82 | 83 | |
| 84 | +**Note**: For English, go to 編集 → 設定 (or press CTRL + ,), and set "言語/Language" to "English". | |
| 85 | + | |
| 83 | 86 | - **コンパイル**: F5 又は「ビルド→コンパイル」 |
| 84 | -- **保存**: Ctrl+s 又は「ファイル→保存」 | |
| 87 | +- **保存**: Ctrl+S 又は「ファイル→保存」 | |
| 85 | 88 | - 右側のパネルでシェーダーコードを編集出来ます |
| 86 | 89 | )"); |
| 90 | + } else { | |
| 91 | + ImGui::TextWrapped("Welcome to Shader Playground!"); | |
| 92 | + ImGui::Dummy(ImVec2(0, 10)); | |
| 93 | + renderMarkdown(R"(# Basic operation | |
| 94 | + | |
| 95 | +**注意**: 日本語で読むには、「Edit → Settings」をクリックし(若しくはCTRL + ,を押し)、「言語/Language」から「日本語」を御選択下さい。". | |
| 96 | + | |
| 97 | +- **Compile**: F5 or "Build → Compile" | |
| 98 | +- **Save**: Ctrl+S or "File → Save" | |
| 99 | +- You can edit the shader code in the right side panel | |
| 100 | +)"); | |
| 101 | + } | |
| 87 | 102 | } else if (m_CurPage == "shader") { |
| 88 | 103 | ImGui::TextWrapped("Shader Playgroundへようこそ!"); |
| 89 | 104 | ImGui::Dummy(ImVec2(0, 10)); |
| 90 | - renderMarkdown(R"(# 基本操作 | |
| 91 | - | |
| 92 | -- **コンパイル**: F5 又は「ビルド→コンパイル」 | |
| 93 | -- **保存**: Ctrl+s 又は「ファイル→保存」 | |
| 94 | -- 右側のパネルでシェーダーコードを編集出来ます | |
| 105 | + renderMarkdown(R"(# シェーダー | |
| 95 | 106 | )"); |
| 96 | 107 | } else if (m_CurPage == "lua") { |
| 97 | 108 | ImGui::TextWrapped("Lua"); |
| 98 | 109 | ImGui::Dummy(ImVec2(0, 10)); |
| 99 | 110 | renderMarkdown(R"(# Luaエンジン |
| 100 | - | |
| 101 | -- **コンパイル**: F5 又は「ビルド→コンパイル」 | |
| 102 | -- **保存**: Ctrl+s 又は「ファイル→保存」 | |
| 103 | -- 右側のパネルでシェーダーコードを編集出来ます | |
| 104 | 111 | )"); |
| 105 | 112 | } else if (m_CurPage == "hotkeys") { |
| 106 | 113 | ImGui::TextWrapped("Shader Playgroundへようこそ!"); |
| 107 | 114 | ImGui::Dummy(ImVec2(0, 10)); |
| 108 | - renderMarkdown(R"(# 基本操作 | |
| 109 | - | |
| 110 | -- **コンパイル**: F5 又は「ビルド→コンパイル」 | |
| 111 | -- **保存**: Ctrl+s 又は「ファイル→保存」 | |
| 112 | -- 右側のパネルでシェーダーコードを編集出来ます | |
| 115 | + renderMarkdown(R"(# ホットキー | |
| 113 | 116 | )"); |
| 114 | 117 | } |
| 115 | 118 |
| @@ -119,9 +119,9 @@ | ||
| 119 | 119 | ImGui::End(); |
| 120 | 120 | |
| 121 | 121 | if (ImGui::BeginMainMenuBar()) { |
| 122 | - if (ImGui::BeginMenu("ファイル")) { | |
| 123 | - if (ImGui::MenuItem("新しいファイル", "CTRL+N", false, false)) {} | |
| 124 | - if (ImGui::MenuItem("開く", "Ctrl+O", false, false)) {} | |
| 122 | + if (ImGui::BeginMenu(glfw.i18n->GetWord("file").c_str())) { | |
| 123 | + if (ImGui::MenuItem(glfw.i18n->GetWord("filenew").c_str(), "CTRL+N", false, false)) {} | |
| 124 | + if (ImGui::MenuItem(glfw.i18n->GetWord("fileopen").c_str(), "Ctrl+O", false, false)) {} | |
| 125 | 125 | //if (ImGui::BeginMenu("最近使った物")) { |
| 126 | 126 | // ImGui::MenuItem("fish_hat.c"); |
| 127 | 127 | // ImGui::MenuItem("fish_hat.inl"); |
| @@ -133,52 +133,58 @@ | ||
| 133 | 133 | // } |
| 134 | 134 | // ImGui::EndMenu(); |
| 135 | 135 | //} |
| 136 | - if (ImGui::MenuItem("保存", "Ctrl+S")) { | |
| 136 | + if (ImGui::MenuItem(glfw.i18n->GetWord("filesave").c_str(), "Ctrl+S")) { | |
| 137 | 137 | glfw.save(); |
| 138 | 138 | } |
| 139 | 139 | |
| 140 | 140 | ImGui::Separator(); |
| 141 | - if (ImGui::MenuItem("終了", "CTRL+Q")) { | |
| 141 | + if (ImGui::MenuItem(glfw.i18n->GetWord("quit").c_str(), "CTRL+Q")) { | |
| 142 | 142 | glfw.isRunning = false; |
| 143 | 143 | } |
| 144 | 144 | ImGui::EndMenu(); |
| 145 | 145 | } |
| 146 | 146 | |
| 147 | - if (ImGui::BeginMenu("編集")) { | |
| 148 | - if (ImGui::MenuItem("元に戻す", "Ctrl+Z", false, false)) {} | |
| 149 | - if (ImGui::MenuItem("やり直し", "Ctrl+Y", false, false)) {} | |
| 147 | + if (ImGui::BeginMenu(glfw.i18n->GetWord("edit").c_str())) { | |
| 148 | + if (ImGui::MenuItem(glfw.i18n->GetWord("editundo").c_str(), "Ctrl+Z", false, false)) {} | |
| 149 | + if (ImGui::MenuItem(glfw.i18n->GetWord("editredo").c_str(), "Ctrl+Y", false, false)) {} | |
| 150 | 150 | ImGui::Separator(); |
| 151 | - if (ImGui::MenuItem("切り取り", "Ctrl+X", false, false)) {} | |
| 152 | - if (ImGui::MenuItem("コピー", "Ctrl+C", false, false)) {} | |
| 153 | - if (ImGui::MenuItem("貼り付け", "Ctrl+V", false, false)) {} | |
| 154 | - if (ImGui::MenuItem("行を複製", "Ctrl+D", false, false)) {} | |
| 151 | + if (ImGui::MenuItem(glfw.i18n->GetWord("editcut").c_str(), "Ctrl+X", false, false)) {} | |
| 152 | + if (ImGui::MenuItem(glfw.i18n->GetWord("editcopy").c_str(), "Ctrl+C", false, false)) {} | |
| 153 | + if (ImGui::MenuItem(glfw.i18n->GetWord("editpaste").c_str(), "Ctrl+V", false, false)) {} | |
| 154 | + if (ImGui::MenuItem(glfw.i18n->GetWord("editduplicate").c_str(), "Ctrl+D", false, false)) {} | |
| 155 | 155 | ImGui::Separator(); |
| 156 | - if (ImGui::MenuItem("設定", "Ctrl+,", false, false)) { | |
| 156 | + if (ImGui::MenuItem(glfw.i18n->GetWord("settings").c_str(), "Ctrl+,", false, false)) { | |
| 157 | 157 | glfw.isSettings = true; |
| 158 | 158 | } |
| 159 | 159 | ImGui::EndMenu(); |
| 160 | 160 | } |
| 161 | 161 | |
| 162 | - if (ImGui::BeginMenu("ビルド")) { | |
| 163 | - if (ImGui::MenuItem("コンパイル", "F5")) { | |
| 162 | + if (ImGui::BeginMenu(glfw.i18n->GetWord("build").c_str())) { | |
| 163 | + if (ImGui::MenuItem(glfw.i18n->GetWord("buildcompile").c_str(), "F5")) { | |
| 164 | 164 | glfw.compile(); |
| 165 | 165 | } |
| 166 | 166 | ImGui::EndMenu(); |
| 167 | 167 | } |
| 168 | 168 | |
| 169 | - if (ImGui::BeginMenu("ヘルプ")) { | |
| 170 | - if (ImGui::MenuItem("マニュアル", "F1")) { | |
| 169 | + if (ImGui::BeginMenu(glfw.i18n->GetWord("help").c_str())) { | |
| 170 | + if (ImGui::MenuItem(glfw.i18n->GetWord("helpmanual").c_str(), "F1")) { | |
| 171 | 171 | glfw.isManual = true; |
| 172 | 172 | } |
| 173 | - if (ImGui::MenuItem("Shader Playgroundについて", "CTRL+H")) { | |
| 173 | + if (ImGui::MenuItem(glfw.i18n->GetWord("helpabout").c_str(), "CTRL+H")) { | |
| 174 | 174 | glfw.isAbout = true; |
| 175 | 175 | } |
| 176 | 176 | ImGui::EndMenu(); |
| 177 | 177 | } |
| 178 | 178 | |
| 179 | 179 | if (ImGui::BeginMenu("言語/Language")) { |
| 180 | - if (ImGui::MenuItem("日本語")) {} | |
| 181 | - if (ImGui::MenuItem("English")) {} | |
| 180 | + if (ImGui::MenuItem("日本語")) { | |
| 181 | + glfw.i18n->SetLanguage(1); | |
| 182 | + glfw.isLangChange = true; | |
| 183 | + } | |
| 184 | + if (ImGui::MenuItem("English")) { | |
| 185 | + glfw.i18n->SetLanguage(2); | |
| 186 | + glfw.isLangChange = true; | |
| 187 | + } | |
| 182 | 188 | ImGui::EndMenu(); |
| 183 | 189 | } |
| 184 | 190 | |
| @@ -186,13 +192,14 @@ | ||
| 186 | 192 | |
| 187 | 193 | f32 rightW = ImGui::GetContentRegionAvail().x; |
| 188 | 194 | ImGui::SetCursorPosX(ImGui::GetCursorPosX() + rightW - 400.f); |
| 189 | - ImGui::Text("現在フレームレート: %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); | |
| 195 | + string fmt = glfw.i18n->GetWord("currentfps") + ": %.3f ms/frame (%.1f FPS)"; | |
| 196 | + ImGui::Text(fmt.c_str(), 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); | |
| 190 | 197 | |
| 191 | 198 | ImGui::SameLine(); |
| 192 | 199 | char timeBuf[64]; |
| 193 | 200 | time_t now = time(nullptr); |
| 194 | 201 | tm *local = localtime(&now); |
| 195 | - strftime(timeBuf, sizeof(timeBuf), "%Y年%m月日%d日 %H:%M:%S", local); | |
| 202 | + strftime(timeBuf, sizeof(timeBuf), glfw.i18n->GetWord("dateformat").c_str(), local); | |
| 196 | 203 | ImGui::Text(" | %s", timeBuf); |
| 197 | 204 | |
| 198 | 205 | ImGui::EndMainMenuBar(); |
| @@ -114,7 +114,8 @@ | ||
| 114 | 114 | } |
| 115 | 115 | |
| 116 | 116 | void ViewPort::Draw(GlfwInfo &glfw) { |
| 117 | - ImGui::Begin("ビューポート###View"); | |
| 117 | + string tit = glfw.i18n->GetWord("editorviewport") + "###View"; | |
| 118 | + ImGui::Begin(tit.c_str()); | |
| 118 | 119 | |
| 119 | 120 | ImVec2 avail = ImGui::GetContentRegionAvail(); |
| 120 | 121 | if ((i32)avail.x != m_Width || (i32)avail.y != m_Height) { |