多言語対応

守矢諏訪子 <suwako@076.moe>、2026-06-27 00:20
コミット bab9c874cb2da716bc0e91aa5e87644ece1d2b5d ・親 ac8090d ・ファイルを見る

13個のファイル、+442 -78

@@ -38,3 +38,4 @@
3838
3939 #include <database/setup.hh>
4040 #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 @@
3737 #pragma once
3838
3939 #include <util/types.hh>
40+#include <util/structs.hh>
4041
4142 namespace gui {
4243 enum class LogType {
@@ -54,7 +55,7 @@
5455 class ConsoleLog {
5556 public:
5657 void Add(LogEntry entry);
57- void Draw(cstr title = "コンソール");
58+ void Draw(GlfwInfo &info, cstr title = "コンソール");
5859 void Clear();
5960
6061 private:
@@ -58,6 +58,8 @@
5858 void SetCode(string code);
5959 void RenderStatusBar(TextEditor editor);
6060
61+ void SetTitle(const string &title) { m_Title = title; }
62+
6163 private:
6264 TextEditor m_Editor;
6365 ImFont *m_CjkFont;
@@ -37,6 +37,7 @@
3737 #pragma once
3838
3939 #include <util/glfwpp.hh>
40+#include <database/locale.hh>
4041 #include <version.hh>
4142 #include <functional>
4243 #include <string>
@@ -49,8 +50,11 @@
4950 bool isAbout = false;
5051 bool isManual = false;
5152 bool isSettings = false;
53+ bool isLangChange = false;
5254
5355 std::string version = version::full;
56+
57+ db::Locale *i18n;
5458
5559 std::function<void()> compile;
5660 std::function<void()> save;
変更 main.cc
@@ -102,6 +102,10 @@
102102 return -1;
103103 }
104104
105+ // 言語
106+ db::Locale i18n(&db);
107+ glfwInfo.i18n = &i18n;
108+
105109 // コンソール
106110 gui::ConsoleLog cmd;
107111
@@ -128,13 +132,13 @@
128132 LUA = c.luaCode;
129133 }
130134
131- string title = shaderName + "(頂点シェーダー)";
135+ string title = shaderName + "(" + glfwInfo.i18n->GetWord("editorvertshader") + ")";
132136 gui::Editor vertEditor((title + "###VertexEditor").c_str(), "VertexEditor", VERT.code, gui::Glsl, ge.GetCjkFont(), ge.GetMonoFont());
133137
134- title = shaderName + "(フラグメントシェーダー)";
138+ title = shaderName + "(" + glfwInfo.i18n->GetWord("editorfragshader") + ")";
135139 gui::Editor fragEditor((title + "###FragmentEditor").c_str(), "FragmentEditor", FRAG.code, gui::Glsl, ge.GetCjkFont(), ge.GetMonoFont());
136140
137- title = shaderName + "(Luaエディター)";
141+ title = shaderName + "(" + glfwInfo.i18n->GetWord("editorlua") + ")";
138142 gui::Editor luaEditor((title + "###LuaEditor").c_str(), "LuaEditor", LUA.code, gui::Lua, ge.GetCjkFont(), ge.GetMonoFont());
139143
140144 // シェーダーをコンパイル
@@ -179,9 +183,9 @@
179183 if (!luaEngine.Validate(LUA.code)) {
180184 gui::LogEntry entry;
181185 entry.type = gui::LogType::Error;
182- entry.text = "Luaコードが不正ですので、コンパイル出来ませんでした。";
186+ entry.text = glfwInfo.i18n->GetWord("consolelogerrorluainvalidcomp");
183187 cmd.Add(entry);
184- std::cout << entry.text << std::endl;
188+ std::cerr << entry.text << std::endl;
185189 return;
186190 }
187191 } catch (const std::exception &e) {
@@ -189,7 +193,11 @@
189193 entry.type = gui::LogType::Error;
190194 entry.text = e.what();
191195 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;
193201 }
194202
195203 shaderProgram.Reload(VERT.code, FRAG.code);
@@ -224,19 +232,22 @@
224232 glEnableVertexAttribArray(a.location);
225233 }
226234 }
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;
227241 } catch (const std::exception &e) {
228242 gui::LogEntry entry;
229243 entry.type = gui::LogType::Error;
230244 entry.text = e.what();
231245 cmd.Add(entry);
232246 std::cerr << entry.text << std::endl;
247+ entry.text = glfwInfo.i18n->GetWord("consolelogerrorluainvalidcomp");
248+ cmd.Add(entry);
249+ std::cerr << entry.text << std::endl;
233250 }
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;
240251 };
241252
242253 glfwInfo.save = [&]() {
@@ -245,31 +256,46 @@
245256 LUA.code = luaEditor.Get().GetText();
246257
247258 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) {
249269 gui::LogEntry entry;
250270 entry.type = gui::LogType::Error;
251- entry.text = "Luaコードが不正ですので、保存出来ませんでした。";
271+ entry.text = e.what();
252272 cmd.Add(entry);
253273 std::cout << entry.text << std::endl;
274+ entry.text = glfwInfo.i18n->GetWord("consolelogerrorluainvalidsave");
275+ cmd.Add(entry);
276+ std::cout << entry.text << std::endl;
254277 return;
255278 }
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;
256289 } catch (const std::exception &e) {
257290 gui::LogEntry entry;
258291 entry.type = gui::LogType::Error;
259292 entry.text = e.what();
260293 cmd.Add(entry);
261294 std::cout << entry.text << std::endl;
295+ entry.text = glfwInfo.i18n->GetWord("consolelogerrorluainvalidsave");
296+ cmd.Add(entry);
297+ std::cout << entry.text << std::endl;
262298 }
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;
273299 };
274300
275301 glfw.PollEvents();
@@ -310,6 +336,16 @@
310336
311337 isHold = isSaveKey || isCompileKey || isAboutKey || isQuitKey || isManualKey || isSettingsKey;
312338
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+
313349 if (window.GetAttrib(glfwpp::Attributes::Iconified) != 0) {
314350 ImGui_ImplGlfw_Sleep(10);
315351 continue;
@@ -324,7 +360,7 @@
324360 vertEditor.Render();
325361 fragEditor.Render();
326362 luaEditor.Render();
327- cmd.Draw();
363+ cmd.Draw(glfwInfo, glfwInfo.i18n->GetWord("editorconsole").c_str());
328364 about.Draw(glfwInfo);
329365 manual.Draw(glfwInfo);
330366
@@ -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 @@
4343 #include <iostream>
4444
4545 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+
4671 void Initialize(sqlitepp::Instance &db) {
4772 cstr sql = R"(CREATE TABLE IF NOT EXISTS version (
4873 id INTEGER PRIMARY KEY,
@@ -97,7 +122,28 @@
97122 UPDATE shader_code
98123 SET updated_at = CURRENT_TIMESTAMP
99124 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+);)";
101147
102148 db.Exec(sql);
103149
@@ -118,6 +164,86 @@
118164 stmt.BindInt(3, version::revision);
119165
120166 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.");
121247 }
122248
123249 i64 shaderId = 0;
@@ -43,10 +43,17 @@
4343
4444 ImGuiWindowFlags flags = 0;
4545 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);
4747 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+ }
5057
5158 ImGui::SetWindowFontScale(1.8f);
5259 ImGui::Text(name.c_str());
@@ -42,9 +42,9 @@
4242 m_Lines.push_back(entry);
4343 }
4444
45- void ConsoleLog::Draw(cstr title) {
45+ void ConsoleLog::Draw(GlfwInfo &info, cstr title) {
4646 ImGui::Begin((string(title) + "###Console").c_str());
47- if (ImGui::Button("消す")) Clear();
47+ if (ImGui::Button(info.i18n->GetWord("editorconsoleclear").c_str())) Clear();
4848 ImGui::Separator();
4949 for (const auto &line : m_Lines) {
5050 if (line.type == LogType::Error)
@@ -45,25 +45,25 @@
4545 ImGuiWindowFlags flags = ImGuiWindowFlags_NoCollapse;
4646
4747 ImGui::SetNextWindowSize({ 600, 700 }, ImGuiCond_Appearing);
48- ImGui::Begin("マニュアル", &glfw.isManual, flags);
48+ ImGui::Begin(glfw.i18n->GetWord("helpmanual").c_str(), &glfw.isManual, flags);
4949
5050 if (ImGui::BeginTabBar("ManualTabs")) {
51- if (ImGui::BeginTabItem("概要")) {
51+ if (ImGui::BeginTabItem(glfw.i18n->GetWord("helpmanualoverview").c_str())) {
5252 m_CurPage = "index";
5353 ImGui::EndTabItem();
5454 }
5555
56- if (ImGui::BeginTabItem("シューだー")) {
56+ if (ImGui::BeginTabItem(glfw.i18n->GetWord("helpmanualshader").c_str())) {
5757 m_CurPage = "shader";
5858 ImGui::EndTabItem();
5959 }
6060
61- if (ImGui::BeginTabItem("Luaエンジン")) {
61+ if (ImGui::BeginTabItem(glfw.i18n->GetWord("helpmanuallua").c_str())) {
6262 m_CurPage = "lua";
6363 ImGui::EndTabItem();
6464 }
6565
66- if (ImGui::BeginTabItem("ホットキー")) {
66+ if (ImGui::BeginTabItem(glfw.i18n->GetWord("helpmanualhotkey").c_str())) {
6767 m_CurPage = "hotkeys";
6868 ImGui::EndTabItem();
6969 }
@@ -76,40 +76,43 @@
7676 ImGui::BeginChild("ManualContent", ImVec2(0, 0), 0, ImGuiWindowFlags_HorizontalScrollbar);
7777
7878 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"(# 基本操作
8283
84+**Note**: For English, go to 編集 → 設定 (or press CTRL + ,), and set "言語/Language" to "English".
85+
8386 - **コンパイル**: F5 又は「ビルド→コンパイル」
84-- **保存**: Ctrl+s 又は「ファイル→保存」
87+- **保存**: Ctrl+S 又は「ファイル→保存」
8588 - 右側のパネルでシェーダーコードを編集出来ます
8689 )");
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+ }
87102 } else if (m_CurPage == "shader") {
88103 ImGui::TextWrapped("Shader Playgroundへようこそ!");
89104 ImGui::Dummy(ImVec2(0, 10));
90- renderMarkdown(R"(# 基本操作
91-
92-- **コンパイル**: F5 又は「ビルド→コンパイル」
93-- **保存**: Ctrl+s 又は「ファイル→保存」
94-- 右側のパネルでシェーダーコードを編集出来ます
105+ renderMarkdown(R"(# シェーダー
95106 )");
96107 } else if (m_CurPage == "lua") {
97108 ImGui::TextWrapped("Lua");
98109 ImGui::Dummy(ImVec2(0, 10));
99110 renderMarkdown(R"(# Luaエンジン
100-
101-- **コンパイル**: F5 又は「ビルド→コンパイル」
102-- **保存**: Ctrl+s 又は「ファイル→保存」
103-- 右側のパネルでシェーダーコードを編集出来ます
104111 )");
105112 } else if (m_CurPage == "hotkeys") {
106113 ImGui::TextWrapped("Shader Playgroundへようこそ!");
107114 ImGui::Dummy(ImVec2(0, 10));
108- renderMarkdown(R"(# 基本操作
109-
110-- **コンパイル**: F5 又は「ビルド→コンパイル」
111-- **保存**: Ctrl+s 又は「ファイル→保存」
112-- 右側のパネルでシェーダーコードを編集出来ます
115+ renderMarkdown(R"(# ホットキー
113116 )");
114117 }
115118
@@ -119,9 +119,9 @@
119119 ImGui::End();
120120
121121 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)) {}
125125 //if (ImGui::BeginMenu("最近使った物")) {
126126 // ImGui::MenuItem("fish_hat.c");
127127 // ImGui::MenuItem("fish_hat.inl");
@@ -133,52 +133,58 @@
133133 // }
134134 // ImGui::EndMenu();
135135 //}
136- if (ImGui::MenuItem("保存", "Ctrl+S")) {
136+ if (ImGui::MenuItem(glfw.i18n->GetWord("filesave").c_str(), "Ctrl+S")) {
137137 glfw.save();
138138 }
139139
140140 ImGui::Separator();
141- if (ImGui::MenuItem("終了", "CTRL+Q")) {
141+ if (ImGui::MenuItem(glfw.i18n->GetWord("quit").c_str(), "CTRL+Q")) {
142142 glfw.isRunning = false;
143143 }
144144 ImGui::EndMenu();
145145 }
146146
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)) {}
150150 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)) {}
155155 ImGui::Separator();
156- if (ImGui::MenuItem("設定", "Ctrl+,", false, false)) {
156+ if (ImGui::MenuItem(glfw.i18n->GetWord("settings").c_str(), "Ctrl+,", false, false)) {
157157 glfw.isSettings = true;
158158 }
159159 ImGui::EndMenu();
160160 }
161161
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")) {
164164 glfw.compile();
165165 }
166166 ImGui::EndMenu();
167167 }
168168
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")) {
171171 glfw.isManual = true;
172172 }
173- if (ImGui::MenuItem("Shader Playgroundについて", "CTRL+H")) {
173+ if (ImGui::MenuItem(glfw.i18n->GetWord("helpabout").c_str(), "CTRL+H")) {
174174 glfw.isAbout = true;
175175 }
176176 ImGui::EndMenu();
177177 }
178178
179179 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+ }
182188 ImGui::EndMenu();
183189 }
184190
@@ -186,13 +192,14 @@
186192
187193 f32 rightW = ImGui::GetContentRegionAvail().x;
188194 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);
190197
191198 ImGui::SameLine();
192199 char timeBuf[64];
193200 time_t now = time(nullptr);
194201 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);
196203 ImGui::Text(" | %s", timeBuf);
197204
198205 ImGui::EndMainMenuBar();
@@ -114,7 +114,8 @@
114114 }
115115
116116 void ViewPort::Draw(GlfwInfo &glfw) {
117- ImGui::Begin("ビューポート###View");
117+ string tit = glfw.i18n->GetWord("editorviewport") + "###View";
118+ ImGui::Begin(tit.c_str());
118119
119120 ImVec2 avail = ImGui::GetContentRegionAvail();
120121 if ((i32)avail.x != m_Width || (i32)avail.y != m_Height) {