Complete Thai and English translations across the UI
Deploy GaC-Srtool to GitHub Pages / deploy (push) Waiting to run

This commit is contained in:
CAtlas0784 committed 2026-11-04 17:26:51 +07:00
1 parent 1593581729
commit 02be735377
9 files changed
+131 -51

No files matched your search

+21 -6
View File
@@ -341,8 +341,8 @@ battle_config = {
| ไฟล์ | เวอร์ชันใหม่ | | ไฟล์ | เวอร์ชันใหม่ |
| :--- | :--- | | :--- | :--- |
| `css/styles.css` | `2.36` | | `css/styles.css` | `2.36` |
| `js/app.js` | `3.32` | | `js/app.js` | `3.33` |
| `js/i18n.js` | `2.26` (ทุก importer ต้องตรงกัน) | | `js/i18n.js` | `2.27` (ทุก importer ต้องตรงกัน) |
| `js/endgame.js` | `1` (ไฟล์ใหม่) | | `js/endgame.js` | `1` (ไฟล์ใหม่) |
Importer ของ `i18n.js` ที่ถูกแก้: `index.html` (ทาง `app.js`), `js/app.js`, `js/avatars-data.js`, `js/equipment-data.js`, `js/item-catalog.js`, `js/unstuck.js` Importer ของ `i18n.js` ที่ถูกแก้: `index.html` (ทาง `app.js`), `js/app.js`, `js/avatars-data.js`, `js/equipment-data.js`, `js/item-catalog.js`, `js/unstuck.js`
@@ -358,6 +358,7 @@ Importer ของ `i18n.js` ที่ถูกแก้: `index.html` (ทา
| `scripts/build-firefly-endgame-data.cjs` | สร้างแคตตาล็อกจาก API ต้นฉบับ | | `scripts/build-firefly-endgame-data.cjs` | สร้างแคตตาล็อกจาก API ต้นฉบับ |
| `assets/firefly-endgame.json` | แคตตาล็อกพร้อมใช้ (~604 KB) | | `assets/firefly-endgame.json` | แคตตาล็อกพร้อมใช้ (~604 KB) |
| `tests/endgame.test.cjs` | unit tests ของ endgame helpers (8 tests) | | `tests/endgame.test.cjs` | unit tests ของ endgame helpers (8 tests) |
| `tests/i18n-coverage.test.cjs` | ตรวจว่า `data-i18n*` ทุกคีย์ resolve ได้ทั้ง th/en |
### แก้ไข ### แก้ไข
| ไฟล์ | สิ่งที่แก้ | | ไฟล์ | สิ่งที่แก้ |
@@ -368,7 +369,7 @@ Importer ของ `i18n.js` ที่ถูกแก้: `index.html` (ทา
| `js/firefly.js` | normalize `blessings` / `monsters` ให้ตรง zod | | `js/firefly.js` | normalize `blessings` / `monsters` ให้ตรง zod |
| `js/i18n.js` | เพิ่ม 46 คีย์ th/en, bump version | | `js/i18n.js` | เพิ่ม 46 คีย์ th/en, bump version |
| `css/styles.css` | สไตล์ `.ff-endgame-*` + responsive | | `css/styles.css` | สไตล์ `.ff-endgame-*` + responsive |
| `js/avatars-data.js`, `js/equipment-data.js`, `js/item-catalog.js`, `js/unstuck.js` | bump `i18n.js?v=2.26` (และ bump version ของ module ปลายทางใน `app.js`) | | `js/avatars-data.js`, `js/equipment-data.js`, `js/item-catalog.js`, `js/unstuck.js` | bump `i18n.js?v=2.27` (และ bump version ของ module ปลายทางใน `app.js`) |
| `tests/firefly-payload.test.cjs` | อัปเดต expected shape + เพิ่ม test normalize | | `tests/firefly-payload.test.cjs` | อัปเดต expected shape + เพิ่ม test normalize |
| `tests/mode-capabilities.test.cjs` | เพิ่ม `fireflyEndgame`, คีย์ i18n, DOM hooks | | `tests/mode-capabilities.test.cjs` | เพิ่ม `fireflyEndgame`, คีย์ i18n, DOM hooks |
@@ -423,8 +424,8 @@ Importer ของ `i18n.js` ที่ถูกแก้: `index.html` (ทา
### 5.6 Tests ### 5.6 Tests
``` ```
node --test tests/*.test.cjs node --test tests/*.test.cjs
ℹ tests 44 ℹ tests 47
ℹ pass 44 ℹ pass 47
ℹ fail 0 ℹ fail 0
``` ```
@@ -433,6 +434,7 @@ node --test tests/*.test.cjs
- การ normalize `blessings` / `monsters` (รวม `dynamic_key`) - การ normalize `blessings` / `monsters` (รวม `dynamic_key`)
- capability matrix + i18n keys + DOM hooks - capability matrix + i18n keys + DOM hooks
- endgame helpers: default/resolve/build/challenge/find-by-stage/custom - endgame helpers: default/resolve/build/challenge/find-by-stage/custom
- **i18n coverage:** ทุก `data-i18n*` ใน `index.html` ต้อง resolve ได้ทั้ง th/en
--- ---
@@ -456,6 +458,18 @@ node --test tests/*.test.cjs
**แก้โดย:** เพิ่ม `applyFireflyEndgameStatus()` ที่อ่าน `state.fireflyEndgame.statusKey/statusState/statusVars` แล้วเรียกที่ท้าย `renderFireflyEndgame()` ทั้งสอง branch (ทั้งตอน catalog ยังไม่โหลดและตอนเรนเดอร์ปกติ) — ตอนนี้สถานะถูกต้องหลังสลับภาษา **แก้โดย:** เพิ่ม `applyFireflyEndgameStatus()` ที่อ่าน `state.fireflyEndgame.statusKey/statusState/statusVars` แล้วเรียกที่ท้าย `renderFireflyEndgame()` ทั้งสอง branch (ทั้งตอน catalog ยังไม่โหลดและตอนเรนเดอร์ปกติ) — ตอนนี้สถานะถูกต้องหลังสลับภาษา
### 6.1.2 🐞 บั๊ก i18n ที่พบเพิ่มเติมและแก้แล้ว
ตรวจทั้งโปรเจกต์ด้วยสคริปต์ (เทียบ `data-i18n*` ใน `index.html` + คีย์จาก `t()` ทุกไฟล์ กับ `TRANSLATIONS.th/en`) พบว่า:
1. **ชื่อธาตุ/สายไม่เคยถูกแปล** — `<option data-i18n="Fire">` แต่พจนานุกรมเก็บไว้ใต้ `elements.Fire` / `paths.Destruction` ทำให้ `t('Fire')` คืนค่าเป็นคีย์เดิม แล้ว `applyTranslations()` ข้ามการอัปเดต (เงื่อนไข `val !== key`) — แก้เป็น `data-i18n="elements.Fire"` และ `data-i18n="paths.Destruction"` ครบทั้ง 34 จุด (7 ธาตุ + 9 สาย × หลาย dropdown)
2. **`fireflyTheoryModeHint` ไม่มีในพจนานุกรมทั้ง th/en** — ข้อความใต้สวิตช์ Creative Mode จึงไม่เคยเปลี่ยนภาษา แก้โดยเพิ่ม th/en
3. **บรรทัดพจนานุกรมไทยถูก merge ข้ามภาษา** — บรรทัดเดียวมีคีย์ `analystDamageAvPlaceholder`, `analystCyclesPlaceholder`, `analystTurnsPlaceholder`, `analystAutoTurnsPlaceholder`, `miniViewFullAnalystTooltip` ซ้ำกัน 13 ภาษา (ค่าที่ชนะคือภาษาโปรตุเกส) และ `copyCardGenerated` หายไป — แยกกลับเป็นค่าภาษาไทยที่ถูกต้อง และเพิ่มคีย์ภาษาอังกฤษที่ขาด
4. **`miniViewFullAnalystTooltip` และ analyst placeholders ไม่มีใน en** — เพิ่มครบแล้ว
เพิ่มเทสต์กันพลาดซ้ำ: `tests/i18n-coverage.test.cjs` ตรวจว่า **ทุก** `data-i18n`, `data-i18n-placeholder`, `data-i18n-title`, `data-i18n-alt`, `data-i18n-aria-label` ใน `index.html` resolve ได้ทั้ง th และ en (รองรับ dotted key) และตรวจ nested `elements.*` / `paths.*`
**ผลตรวจหลังแก้:** data-i18n missing th = 0, en = 0; t() missing th = 0, en = 0
### 6.2 ✅ ทดสอบมือถือ 360–768px ### 6.2 ✅ ทดสอบมือถือ 360–768px
**สถานะ:** ทดสอบด้วย iframe ขนาด 360×800 และ 768×1024 (media query ภายใน iframe ทำงานเหมือน viewport จริง) **สถานะ:** ทดสอบด้วย iframe ขนาด 360×800 และ 768×1024 (media query ภายใน iframe ทำงานเหมือน viewport จริง)
@@ -547,7 +561,8 @@ node scripts/dev-server.cjs
- [x] เปลี่ยนภาษาแล้วหน้า picker รีเรนเดอร์ และสถานะไม่ค้างที่ loading - [x] เปลี่ยนภาษาแล้วหน้า picker รีเรนเดอร์ และสถานะไม่ค้างที่ loading
- [x] มือถือ 360px ไม่มี horizontal scroll และปุ่ม ≥44px - [x] มือถือ 360px ไม่มี horizontal scroll และปุ่ม ≥44px
- [x] โหมด GaCy เดิมยังทำงาน (Monster Setting เดิม, Aha's Help, custom waves) - [x] โหมด GaCy เดิมยังทำงาน (Monster Setting เดิม, Aha's Help, custom waves)
- [x] `?v=` ถูก bump ครบ (`styles.css 2.36`, `app.js 3.32`, `i18n.js 2.26` ทุก importer) - [x] `?v=` ถูก bump ครบ (`styles.css 2.36`, `app.js 3.33`, `i18n.js 2.27` ทุก importer)
- [x] ไม่มีข้อความ hardcode ใหม่ใน HTML/JS - [x] ไม่มีข้อความ hardcode ใหม่ใน HTML/JS
- [x] ทุก `data-i18n*` resolve ได้ทั้ง th/en (มีเทสต์บังคับ)
- [x] ไม่แก้ `README.md` - [x] ไม่แก้ `README.md`
- [ ] ทดสอบ `POST /sync` happy path กับ FireflyGO จริง (ต้องมีเซิร์ฟเวอร์) - [ ] ทดสอบ `POST /sync` happy path กับ FireflyGO จริง (ต้องมีเซิร์ฟเวอร์)
+35 -35
View File
@@ -304,25 +304,25 @@
</select> </select>
<select id="avatar-filter-element" class="form-select" style="max-width: 130px;"> <select id="avatar-filter-element" class="form-select" style="max-width: 130px;">
<option value="all" data-i18n="filterAllElements">ทุกธาตุ</option> <option value="all" data-i18n="filterAllElements">ทุกธาตุ</option>
<option value="Fire" data-i18n="Fire">ไฟ</option> <option value="Fire" data-i18n="elements.Fire">ไฟ</option>
<option value="Ice" data-i18n="Ice">น้ำแข็ง</option> <option value="Ice" data-i18n="elements.Ice">น้ำแข็ง</option>
<option value="Lightning" data-i18n="Lightning">สายฟ้า</option> <option value="Lightning" data-i18n="elements.Lightning">สายฟ้า</option>
<option value="Wind" data-i18n="Wind">ลม</option> <option value="Wind" data-i18n="elements.Wind">ลม</option>
<option value="Physical" data-i18n="Physical">กายภาพ</option> <option value="Physical" data-i18n="elements.Physical">กายภาพ</option>
<option value="Quantum" data-i18n="Quantum">ควอนตัม</option> <option value="Quantum" data-i18n="elements.Quantum">ควอนตัม</option>
<option value="Imaginary" data-i18n="Imaginary">จินตภาพ</option> <option value="Imaginary" data-i18n="elements.Imaginary">จินตภาพ</option>
</select> </select>
<select id="avatar-filter-path" class="form-select" style="max-width: 130px;"> <select id="avatar-filter-path" class="form-select" style="max-width: 130px;">
<option value="all" data-i18n="filterAllPaths">ทุกประเภท</option> <option value="all" data-i18n="filterAllPaths">ทุกประเภท</option>
<option value="Destruction" data-i18n="Destruction">ทำลายล้าง</option> <option value="Destruction" data-i18n="paths.Destruction">ทำลายล้าง</option>
<option value="Hunt" data-i18n="Hunt">ล่าสังหาร</option> <option value="Hunt" data-i18n="paths.Hunt">ล่าสังหาร</option>
<option value="Erudition" data-i18n="Erudition">ปัญญา</option> <option value="Erudition" data-i18n="paths.Erudition">ปัญญา</option>
<option value="Harmony" data-i18n="Harmony">ประสาน</option> <option value="Harmony" data-i18n="paths.Harmony">ประสาน</option>
<option value="Nihility" data-i18n="Nihility">ลบล้าง</option> <option value="Nihility" data-i18n="paths.Nihility">ลบล้าง</option>
<option value="Preservation" data-i18n="Preservation">อนุรักษ์</option> <option value="Preservation" data-i18n="paths.Preservation">อนุรักษ์</option>
<option value="Abundance" data-i18n="Abundance">เฟื่องฟู</option> <option value="Abundance" data-i18n="paths.Abundance">เฟื่องฟู</option>
<option value="Remembrance" data-i18n="Remembrance">ความทรงจำ</option> <option value="Remembrance" data-i18n="paths.Remembrance">ความทรงจำ</option>
<option value="Elation" data-i18n="Elation">ปีติ</option> <option value="Elation" data-i18n="paths.Elation">ปีติ</option>
</select> </select>
</div> </div>
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;"> <div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
@@ -1728,15 +1728,15 @@
<input type="text" id="add-lc-search" class="form-input" data-i18n-placeholder="lightConeSearchPlaceholder" placeholder="🔍 ค้นหากรวยแสง (ชื่อหรือ ID)..." style="flex: 1; min-width: 160px;"> <input type="text" id="add-lc-search" class="form-input" data-i18n-placeholder="lightConeSearchPlaceholder" placeholder="🔍 ค้นหากรวยแสง (ชื่อหรือ ID)..." style="flex: 1; min-width: 160px;">
<select id="add-lc-filter-path" class="form-select" style="max-width: 140px;"> <select id="add-lc-filter-path" class="form-select" style="max-width: 140px;">
<option value="all" data-i18n="filterAllPaths">ทุกสาย (All)</option> <option value="all" data-i18n="filterAllPaths">ทุกสาย (All)</option>
<option value="Destruction" data-i18n="Destruction">ทำลายล้าง</option> <option value="Destruction" data-i18n="paths.Destruction">ทำลายล้าง</option>
<option value="Hunt" data-i18n="Hunt">ล่าสังหาร</option> <option value="Hunt" data-i18n="paths.Hunt">ล่าสังหาร</option>
<option value="Erudition" data-i18n="Erudition">ปัญญา</option> <option value="Erudition" data-i18n="paths.Erudition">ปัญญา</option>
<option value="Harmony" data-i18n="Harmony">ประสาน</option> <option value="Harmony" data-i18n="paths.Harmony">ประสาน</option>
<option value="Nihility" data-i18n="Nihility">ลบล้าง</option> <option value="Nihility" data-i18n="paths.Nihility">ลบล้าง</option>
<option value="Preservation" data-i18n="Preservation">อนุรักษ์</option> <option value="Preservation" data-i18n="paths.Preservation">อนุรักษ์</option>
<option value="Abundance" data-i18n="Abundance">เฟื่องฟู</option> <option value="Abundance" data-i18n="paths.Abundance">เฟื่องฟู</option>
<option value="Remembrance" data-i18n="Remembrance">ความทรงจำ</option> <option value="Remembrance" data-i18n="paths.Remembrance">ความทรงจำ</option>
<option value="Elation" data-i18n="Elation">ปีติ</option> <option value="Elation" data-i18n="paths.Elation">ปีติ</option>
</select> </select>
<select id="add-lc-filter-rarity" class="form-select" style="max-width: 110px;"> <select id="add-lc-filter-rarity" class="form-select" style="max-width: 110px;">
<option value="all" data-i18n="filterAllRarity">ทุกระดับดาว</option> <option value="all" data-i18n="filterAllRarity">ทุกระดับดาว</option>
@@ -1993,15 +1993,15 @@
<select id="lc-picker-filter-path" class="form-select" style="max-width: 140px;"> <select id="lc-picker-filter-path" class="form-select" style="max-width: 140px;">
<option value="all" data-i18n="filterAllPaths">ทุกสาย</option> <option value="all" data-i18n="filterAllPaths">ทุกสาย</option>
<option value="same" selected data-i18n="filterSamePath">ตรงสายตัวละคร</option> <option value="same" selected data-i18n="filterSamePath">ตรงสายตัวละคร</option>
<option value="Destruction" data-i18n="Destruction">ทำลายล้าง</option> <option value="Destruction" data-i18n="paths.Destruction">ทำลายล้าง</option>
<option value="Hunt" data-i18n="Hunt">ล่าสังหาร</option> <option value="Hunt" data-i18n="paths.Hunt">ล่าสังหาร</option>
<option value="Erudition" data-i18n="Erudition">ปัญญา</option> <option value="Erudition" data-i18n="paths.Erudition">ปัญญา</option>
<option value="Harmony" data-i18n="Harmony">ประสาน</option> <option value="Harmony" data-i18n="paths.Harmony">ประสาน</option>
<option value="Nihility" data-i18n="Nihility">ลบล้าง</option> <option value="Nihility" data-i18n="paths.Nihility">ลบล้าง</option>
<option value="Preservation" data-i18n="Preservation">อนุรักษ์</option> <option value="Preservation" data-i18n="paths.Preservation">อนุรักษ์</option>
<option value="Abundance" data-i18n="Abundance">เฟื่องฟู</option> <option value="Abundance" data-i18n="paths.Abundance">เฟื่องฟู</option>
<option value="Remembrance" data-i18n="Remembrance">ความทรงจำ</option> <option value="Remembrance" data-i18n="paths.Remembrance">ความทรงจำ</option>
<option value="Elation" data-i18n="Elation">ปีติ</option> <option value="Elation" data-i18n="paths.Elation">ปีติ</option>
</select> </select>
<select id="lc-picker-rank" class="form-select" style="max-width: 110px;" data-i18n-title="superimpositionSelectTitle" title="ระดับการขัดเกลาเมื่อสวมใส่"> <select id="lc-picker-rank" class="form-select" style="max-width: 110px;" data-i18n-title="superimpositionSelectTitle" title="ระดับการขัดเกลาเมื่อสวมใส่">
<option value="5" selected data-superimposition-rank>ขัดเกลา S5</option> <option value="5" selected data-superimposition-rank>ขัดเกลา S5</option>
@@ -2079,6 +2079,6 @@
<div class="toast-container" id="toast-container"></div> <div class="toast-container" id="toast-container"></div>
<!-- Script Modules --> <!-- Script Modules -->
<script type="module" src="js/app.js?v=3.32"></script> <script type="module" src="js/app.js?v=3.33"></script>
</body> </body>
</html> </html>
+5 -5
View File
@@ -1,10 +1,10 @@
import { COMMANDS, usage, inspectCommand, completeCommand, setCommandLanguage, describeCommand } from './command-assistant.mjs'; import { COMMANDS, usage, inspectCommand, completeCommand, setCommandLanguage, describeCommand } from './command-assistant.mjs';
import { AVATAR_METADATA, getAvatarInfo } from './avatars-data.js?v=2.22'; import { AVATAR_METADATA, getAvatarInfo } from './avatars-data.js?v=2.23';
import { LIGHTCONE_METADATA, RELIC_SET_METADATA, getLightconeInfo, getRelicSetInfo } from './equipment-data.js?v=2.28'; import { LIGHTCONE_METADATA, RELIC_SET_METADATA, getLightconeInfo, getRelicSetInfo } from './equipment-data.js?v=2.29';
import { initializeItemCatalog } from './item-catalog.js?v=6'; import { initializeItemCatalog } from './item-catalog.js?v=7';
import { initializeUnstuck } from './unstuck.js?v=5'; import { initializeUnstuck } from './unstuck.js?v=6';
import { PRYDWEN_BUILD_GUIDES } from './build-guides-data.js'; import { PRYDWEN_BUILD_GUIDES } from './build-guides-data.js';
import { SUPPORTED_LANGUAGES, TRANSLATIONS, getCurrentLanguage, setLanguage, t, translateElement, translatePath } from './i18n.js?v=2.26'; import { SUPPORTED_LANGUAGES, TRANSLATIONS, getCurrentLanguage, setLanguage, t, translateElement, translatePath } from './i18n.js?v=2.27';
import { enkaToFreeSR, mergeFreeSR, countFreeSRContent, normalizeEnkaUid } from './enka-import.js?v=1'; import { enkaToFreeSR, mergeFreeSR, countFreeSRContent, normalizeEnkaUid } from './enka-import.js?v=1';
import { buildSyncPayload, normalizeExtraData, fireflySyncUrl, FIREFLY_EXTRA_FALLBACK } from './firefly.js?v=1'; import { buildSyncPayload, normalizeExtraData, fireflySyncUrl, FIREFLY_EXTRA_FALLBACK } from './firefly.js?v=1';
import { MODE_LABELS, normalizeMode, modeCapabilities, modeSupports } from './modes.js?v=1'; import { MODE_LABELS, normalizeMode, modeCapabilities, modeSupports } from './modes.js?v=1';
+1 -1
View File
@@ -1,4 +1,4 @@
import { getCurrentLanguage, translateElement, translatePath } from "./i18n.js?v=2.26"; import { getCurrentLanguage, translateElement, translatePath } from "./i18n.js?v=2.27";
export const AVATAR_METADATA = { export const AVATAR_METADATA = {
"1001": { "1001": {
+1 -1
View File
@@ -1,4 +1,4 @@
import { getCurrentLanguage, translatePath } from "./i18n.js?v=2.26"; import { getCurrentLanguage, translatePath } from "./i18n.js?v=2.27";
export const LIGHTCONE_METADATA = { export const LIGHTCONE_METADATA = {
"20000": { "20000": {
+13 -1
View File
@@ -7255,12 +7255,18 @@ const MODAL_AND_TOOL_TRANSLATIONS = {
relicSetSearchLabel: 'ค้นหาชุดรีลิกส์', relicSetSearchLabel: 'ค้นหาชุดรีลิกส์',
relicSetSearchPlaceholder: '🔍 ค้นหาชุดรีลิกส์...', relicSetSearchPlaceholder: '🔍 ค้นหาชุดรีลิกส์...',
relicSetGridLabel: 'ชุดรีลิกส์', relicSetGridLabel: 'ชุดรีลิกส์',
analystDamageAvPlaceholder: "เฉลี่ยต่อรอบ: -", analystCyclesPlaceholder: "0 รอบ", analystTurnsPlaceholder: "0 เทิร์น", analystAutoTurnsPlaceholder: "เทิร์นอัตโนมัติ: 0", analystDamageAvPlaceholder: "Avg per cycle: -", analystCyclesPlaceholder: "0 Cycles", analystTurnsPlaceholder: "0 Turns", analystAutoTurnsPlaceholder: "Auto Turns: 0", analystDamageAvPlaceholder: "ラウンド平均: -", analystCyclesPlaceholder: "0 ラウンド", analystTurnsPlaceholder: "0 ターン", analystAutoTurnsPlaceholder: "オートターン: 0", analystDamageAvPlaceholder: "라운드당 평균: -", analystCyclesPlaceholder: "0 라운드", analystTurnsPlaceholder: "0 턴", analystAutoTurnsPlaceholder: "자동 턴: 0", analystDamageAvPlaceholder: "Trung bình mỗi vòng: -", analystCyclesPlaceholder: "0 vòng", analystTurnsPlaceholder: "0 lượt", analystAutoTurnsPlaceholder: "Lượt tự động: 0", analystDamageAvPlaceholder: "Rata-rata per siklus: -", analystCyclesPlaceholder: "0 Siklus", analystTurnsPlaceholder: "0 Giliran", analystAutoTurnsPlaceholder: "Giliran otomatis: 0", analystDamageAvPlaceholder: "Promedio por ciclo: -", analystCyclesPlaceholder: "0 ciclos", analystTurnsPlaceholder: "0 turnos", analystAutoTurnsPlaceholder: "Turnos automáticos: 0", analystDamageAvPlaceholder: "Moyenne par cycle : -", analystCyclesPlaceholder: "0 cycle", analystTurnsPlaceholder: "0 tour", analystAutoTurnsPlaceholder: "Tours auto : 0", analystDamageAvPlaceholder: "Durchschnitt pro Zyklus: -", analystCyclesPlaceholder: "0 Zyklen", analystTurnsPlaceholder: "0 Züge", analystAutoTurnsPlaceholder: "Auto-Züge: 0", analystDamageAvPlaceholder: "В среднем за цикл: -", analystCyclesPlaceholder: "0 циклов", analystTurnsPlaceholder: "0 ходов", analystAutoTurnsPlaceholder: "Авто-ходы: 0", analystDamageAvPlaceholder: "Média por ciclo: -", analystCyclesPlaceholder: "0 ciclos", analystTurnsPlaceholder: "0 turnos", analystAutoTurnsPlaceholder: "Turnos automáticos: 0", miniViewFullAnalystTooltip: "เปิดหน้า Damage Analyst ตัวเต็ม", miniViewFullAnalystTooltip: "Open full Damage Analyst", miniViewFullAnalystTooltip: "完全なダメージ分析を開く", miniViewFullAnalystTooltip: "전체 데미지 분석 열기", miniViewFullAnalystTooltip: "Mở Trình phân tích sát thương đầy đủ", miniViewFullAnalystTooltip: "Buka Analis Kerusakan Lengkap", miniViewFullAnalystTooltip: "Abrir analizador de daño completo", miniViewFullAnalystTooltip: "Ouvrir l'analyseur de dégâts complet", miniViewFullAnalystTooltip: "Vollständigen Schadensanalysten öffnen", miniViewFullAnalystTooltip: "Открыть полный анализ урона", miniViewFullAnalystTooltip: "Abrir analisador de dano completo", copyCardGenerated: 'สร้างโดย GaCy-Srtool', analystDamageAvPlaceholder: 'เฉลี่ยต่อรอบ: -',
analystCyclesPlaceholder: '0 รอบ',
analystTurnsPlaceholder: '0 เทิร์น',
analystAutoTurnsPlaceholder: 'เทิร์นอัตโนมัติ: 0',
miniViewFullAnalystTooltip: 'เปิดหน้า Damage Analyst ตัวเต็ม',
copyCardGenerated: 'สร้างโดย GaCy-Srtool',
// FireflyGo endgame room picker // FireflyGo endgame room picker
ffEndgameTitle: '🦋 ห้อง Endgame (FireflyGO)', ffEndgameTitle: '🦋 ห้อง Endgame (FireflyGO)',
ffEndgameTablistLabel: 'โหมดด่าน endgame ของ FireflyGO', ffEndgameTablistLabel: 'โหมดด่าน endgame ของ FireflyGO',
ffEndgameDescription: 'เลือกด่าน endgame และคลื่นมอนที่ FireflyGO จะใช้ แล้วเขียนลง battle_config โดยตรง', ffEndgameDescription: 'เลือกด่าน endgame และคลื่นมอนที่ FireflyGO จะใช้ แล้วเขียนลง battle_config โดยตรง',
ffEndgameRule: 'ค่าเหล่านี้เขียนลง battle_config (และ extra_data.challenge สำหรับ Anomaly Arbitration) จากนั้นบันทึกในเครื่องหรือซิงค์ขึ้น FireflyGO', ffEndgameRule: 'ค่าเหล่านี้เขียนลง battle_config (และ extra_data.challenge สำหรับ Anomaly Arbitration) จากนั้นบันทึกในเครื่องหรือซิงค์ขึ้น FireflyGO',
fireflyTheoryModeHint: 'สวิตช์นี้คือโหมดสร้างสรรค์ (Creative Mode) — เปิดจากที่ไหนก็ได้ อีกสวิตช์จะตามให้อัตโนมัติ',
ffEndgameModePeak: 'Anomaly Arbitration', ffEndgameModePeak: 'Anomaly Arbitration',
ffEndgameLoading: 'กำลังโหลดข้อมูลด่าน endgame…', ffEndgameLoading: 'กำลังโหลดข้อมูลด่าน endgame…',
ffEndgameReady: 'พร้อมใช้งาน · เลือกห้องแล้วกดใช้กับ battle_config', ffEndgameReady: 'พร้อมใช้งาน · เลือกห้องแล้วกดใช้กับ battle_config',
@@ -7512,6 +7518,11 @@ const MODAL_AND_TOOL_TRANSLATIONS = {
relicSetSearchLabel: 'Search relic sets', relicSetSearchLabel: 'Search relic sets',
relicSetSearchPlaceholder: '🔍 Search relic sets...', relicSetSearchPlaceholder: '🔍 Search relic sets...',
relicSetGridLabel: 'Relic sets', relicSetGridLabel: 'Relic sets',
analystDamageAvPlaceholder: 'Avg per cycle: -',
analystCyclesPlaceholder: '0 Cycles',
analystTurnsPlaceholder: '0 Turns',
analystAutoTurnsPlaceholder: 'Auto Turns: 0',
miniViewFullAnalystTooltip: 'Open full Damage Analyst',
copyCardGenerated: 'Generated via GaCy-Srtool', copyCardGenerated: 'Generated via GaCy-Srtool',
// FireflyGo endgame room picker // FireflyGo endgame room picker
@@ -7519,6 +7530,7 @@ const MODAL_AND_TOOL_TRANSLATIONS = {
ffEndgameTablistLabel: 'FireflyGO endgame room modes', ffEndgameTablistLabel: 'FireflyGO endgame room modes',
ffEndgameDescription: 'Pick the endgame room and monster waves FireflyGO should load, then write them straight into battle_config.', ffEndgameDescription: 'Pick the endgame room and monster waves FireflyGO should load, then write them straight into battle_config.',
ffEndgameRule: 'These values are written to battle_config (plus extra_data.challenge for Anomaly Arbitration), then saved locally or synced to FireflyGO.', ffEndgameRule: 'These values are written to battle_config (plus extra_data.challenge for Anomaly Arbitration), then saved locally or synced to FireflyGO.',
fireflyTheoryModeHint: 'This switch is Creative Mode — turn it on anywhere and the other switch follows automatically.',
ffEndgameModePeak: 'Anomaly Arbitration', ffEndgameModePeak: 'Anomaly Arbitration',
ffEndgameLoading: 'Loading endgame data…', ffEndgameLoading: 'Loading endgame data…',
ffEndgameReady: 'Ready · pick a room, then apply it to battle_config', ffEndgameReady: 'Ready · pick a room, then apply it to battle_config',
+1 -1
View File
@@ -1,4 +1,4 @@
import { getCurrentLanguage, t, translatePath } from './i18n.js?v=2.26'; import { getCurrentLanguage, t, translatePath } from './i18n.js?v=2.27';
const CATALOG_URL = new URL('../assets/item-catalog.json?v=2', import.meta.url); const CATALOG_URL = new URL('../assets/item-catalog.json?v=2', import.meta.url);
const PAGE_SIZE = 48; const PAGE_SIZE = 48;
+1 -1
View File
@@ -1,4 +1,4 @@
import { getCurrentLanguage } from './i18n.js?v=2.26'; import { getCurrentLanguage } from './i18n.js?v=2.27';
const strings = { const strings = {
en: { en: {
+53
View File
@@ -0,0 +1,53 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const { pathToFileURL } = require('node:url');
const root = path.join(__dirname, '..');
const html = fs.readFileSync(path.join(root, 'index.html'), 'utf8');
function resolve(dict, key) {
if (typeof dict[key] === 'string') return dict[key];
if (key.includes('.')) return key.split('.').reduce((value, part) => value?.[part], dict);
return undefined;
}
test('every data-i18n key in index.html exists in Thai and English', async () => {
const { TRANSLATIONS } = await import(pathToFileURL(path.join(root, 'js/i18n.js')).href);
const keys = new Set();
const attrRe = /data-i18n(?:-placeholder|-title|-alt|-aria-label)?="([^"]+)"/g;
let match;
while ((match = attrRe.exec(html))) keys.add(match[1].trim());
assert.ok(keys.size > 300, 'index.html should expose a large translation surface');
for (const lang of ['th', 'en']) {
const missing = [...keys].filter((key) => {
const value = resolve(TRANSLATIONS[lang], key);
return typeof value !== 'string' || !value.trim();
});
assert.deepEqual(missing, [], `${lang} is missing data-i18n keys: ${missing.join(', ')}`);
}
});
test('nested element and path dictionaries stay addressable from HTML', async () => {
const { TRANSLATIONS } = await import(pathToFileURL(path.join(root, 'js/i18n.js')).href);
for (const lang of ['th', 'en']) {
for (const key of ['elements.Fire', 'elements.Ice', 'elements.Imaginary', 'paths.Destruction', 'paths.Elation']) {
const value = resolve(TRANSLATIONS[lang], key);
assert.equal(typeof value, 'string', `${lang}.${key} must resolve`);
assert.ok(value.trim().length > 0, `${lang}.${key} must not be empty`);
}
}
assert.equal(html.includes('data-i18n="Fire"'), false);
assert.equal(html.includes('data-i18n="elements.Fire"'), true);
});
test('the shared theory-craft hint is translated', async () => {
const { TRANSLATIONS } = await import(pathToFileURL(path.join(root, 'js/i18n.js')).href);
for (const lang of ['th', 'en']) {
assert.equal(typeof TRANSLATIONS[lang].fireflyTheoryModeHint, 'string');
assert.ok(TRANSLATIONS[lang].fireflyTheoryModeHint.trim().length > 0);
}
});