fix(showcase): responsive card, full localization, in-game stat icons and clean PNG export
Layout: card now fills its container (3/2/1 columns) with no horizontal scrollbar. i18n: every modal/showcase/console string follows the selected language, named fallbacks for th/en plus English fallback for other locales, localized character names and light-cone passive text. Showcase stats use the official in-game property icons (same set as the overview tab) instead of emoji, including the element DMG icon. Export: fixed 1200px clone with poster metrics, cover-cropped splash art (html2canvas ignores object-fit), transparent background so the rounded corners have no dark wedges, and a two-pass measurement inside the capture iframe so the PNG is never cropped.
This commit is contained in:
1 parent
d9b7a4ce1b
commit
76986eed4a
9 files changed
+775
-187
No files matched your search
+207
@@ -7044,6 +7044,213 @@ for (const [language, description] of Object.entries(ITEM_CATALOG_INTERACTION_DE
|
||||
TRANSLATIONS[language].itemCatalogDescription = description;
|
||||
}
|
||||
|
||||
// Strings that used to be hardcoded in the character modal, showcase card,
|
||||
// dashboard console and camera panel. Only th/en are defined here; every other
|
||||
// language falls back to en through t(), so no Thai leaks into other locales.
|
||||
const MODAL_AND_TOOL_TRANSLATIONS = {
|
||||
th: {
|
||||
relicSlotNotEquipped: 'ยังไม่ได้สวมใส่',
|
||||
relicSlotCreateHint: '+ คลิกเพื่อเลือกหรือสร้าง (แนะนำ: {stat})',
|
||||
relicSlotUnequip: 'ถอดชิ้นนี้',
|
||||
relicSubstatFallback: '4 ออปชั่นรอง (คลิกเพื่อปรับแต่ง)',
|
||||
relicSetNoBonus: 'ยังไม่มีเอฟเฟกต์เซ็ตทำงาน (ต้องใส่เซ็ตเดียวกันอย่างน้อย 2 หรือ 4 ชิ้น)',
|
||||
relicSet4Fallback: 'เปิดใช้งานเอฟเฟกต์ครบ 4 ชิ้น',
|
||||
relicSet2Fallback: 'เปิดใช้งานเอฟเฟกต์ 2 ชิ้น',
|
||||
overviewRelicCount: '{count}/6 ชิ้นสวมใส่ (ยังไม่มีโบนัสเซ็ต)',
|
||||
relicPickerEditTitle: '🔧 แก้ไขรีลิกส์: {set} ({slot}) - {name}',
|
||||
relicPickerEditSubtitle: 'ปรับแต่งเซ็ต สเตตัสหลัก และออฟรองของรีลิกส์ชิ้นนี้ (#{id})',
|
||||
relicPickerEditing: '✏️ กำลังแก้ไขรีลิกส์ #{id}',
|
||||
relicPickerSaveChanges: '💾 บันทึกการแก้ไขออฟรองชิ้นนี้',
|
||||
relicPickerEquipNow: '⚡ สวมใส่ชิ้นนี้ทันที (+15 5★ ออฟที่เลือก)',
|
||||
relicPickerInInventory: 'ชิ้นในคลัง',
|
||||
relicPickerEditThis: '✏️ แก้ไขออฟรองชิ้นนี้',
|
||||
relicPickerEditThisTitle: 'โหลดข้อมูลชิ้นนี้ลงมาแก้ไขออฟรอง',
|
||||
relicPickerRecommended: 'ออฟรองแนะนำ 4 แถวเทพ ปรับแต่งได้ตามต้องการด้านล่าง',
|
||||
substatMainConflict: ' (ตรงกับ Main Stat)',
|
||||
substatAlreadyChosen: ' (เลือกแล้ว)',
|
||||
substatSlotLabel: 'ออฟรอง {n}',
|
||||
substatRolls: '+{value} ({count} โรล)',
|
||||
substatDecreaseRoll: 'ลดโรล',
|
||||
substatIncreaseRoll: 'เพิ่มโรล',
|
||||
relicEmptyInventoryTitle: 'ไม่มีรีลิกส์สำหรับช่องนี้ในคลัง',
|
||||
relicEmptyInventoryHint: 'สามารถสลับไปที่แท็บ "⚡ สวมใส่ / สร้างชิ้นแนะนำ" เพื่อสร้างชิ้นใหม่ได้ทันที!',
|
||||
relicEquippedNow: 'กำลังสวมใส่อยู่',
|
||||
relicWornBy: 'สวมใส่โดย: {name}',
|
||||
relicFreeInInventory: 'ว่างในคลัง',
|
||||
relicInUse: 'ใช้งานอยู่',
|
||||
relicSelectThis: 'เลือกชิ้นนี้',
|
||||
lcNoneEquipped: 'ยังไม่ได้สวมใส่ Light Cone',
|
||||
lcSelectPrompt: 'กรุณาเลือก Light Cone ที่ต้องการสวมใส่',
|
||||
lcSamePathGroup: '⭐ Light Cones สาย {path}',
|
||||
lcOtherPathGroup: '🗡️ Light Cones สายอื่นๆ',
|
||||
lcNoResults: 'ไม่พบกรวยแสงตามเงื่อนไขที่ค้นหา',
|
||||
lcEquippedBadge: 'สวมใส่อยู่',
|
||||
lcSignatureBadge: 'ประจำตัว',
|
||||
lcSelectTitle: '🗡️ เลือกสวมใส่กรวยแสง: {name}',
|
||||
eqSkillFallbackTitle: 'สกิลพิเศษประจำกรวยแสง',
|
||||
eqSkillFallbackDesc: 'เพิ่มพลังต่อสู้และเสริมกลไกเฉพาะสายของตัวละครเมื่อติดตั้ง',
|
||||
eqEquippedByLabel: 'ผู้สวมใส่ (Equipped By):',
|
||||
eqSaveWearer: '💾 บันทึกสวมใส่',
|
||||
eqDelete: '🗑️ ลบ',
|
||||
eqDeleteTitle: 'ลบออกจากคลัง',
|
||||
eqSlotN: 'ชิ้นที่ {n}',
|
||||
eqMainStatLabel: 'สเตตัสหลัก (Main Stat)',
|
||||
eqSubstatsLabel: 'ออฟรอง (Substats):',
|
||||
eqNoSubstats: 'ไม่มีข้อมูลออฟรอง',
|
||||
eqEditSubstats: '🔧 ปรับแต่งออฟรอง / สเตตัส (Edit Substats)',
|
||||
eqSet2Fallback: 'เพิ่มประสิทธิภาพตามเซ็ตที่กำหนด',
|
||||
eqSet4Fallback: 'ปลดล็อกเอฟเฟกต์ต่อสู้ขั้นสูง',
|
||||
showcaseActiveSets: 'โบนัสเซ็ตที่ทำงาน',
|
||||
showcaseEquippedRelics: 'รีลิกส์ที่สวมใส่ (+15)',
|
||||
showcaseBuildTag: 'ภาพรวมบิลด์',
|
||||
showcaseRankLevel: 'S{rank} • Lv. {level}/80',
|
||||
consolePreviewHint: 'พิมพ์ / เพื่อเลือกคำสั่ง',
|
||||
consoleDescriptionHint: 'เลือกคำสั่งเพื่อดูตัวอย่างและช่องที่ต้องใส่',
|
||||
consoleArgumentHint: '↑ ↓ เลื่อน · Tab เติมค่าที่เลือก · Esc ปิดรายการ',
|
||||
consoleKeyboardHint: '↑ ↓ เลื่อนรายการ · Tab เติมค่า · Shift+Tab ย้อนรายการ · Esc ปิด · Enter ส่งคำสั่ง',
|
||||
cameraHint: 'เลือกโหมด แล้วกด Tab ในเกมเพื่อเปิดหรือปิดกล้อง',
|
||||
cameraMode: 'โหมดกล้อง',
|
||||
cameraFaceCamDistance: 'ระยะ FaceCam',
|
||||
cameraSave: 'บันทึกกล้อง',
|
||||
cameraFaceCamExperimental: 'FaceCam (ทดลอง)',
|
||||
cameraDistanceError: 'ระยะ FaceCam ต้องอยู่ระหว่าง 0.5 ถึง 5',
|
||||
cameraSaved: 'บันทึกกล้องแล้ว',
|
||||
lcPassiveFallback: 'เพิ่มค่าสถานะพื้นฐานตามสาย <strong>{path}</strong> และเพิ่มประสิทธิภาพความเสียหายและการสนับสนุน <strong>{value}%</strong> เมื่อเข้าสู่การต่อสู้',
|
||||
slotHeadShort: 'ส่วนหัว',
|
||||
slotHandsShort: 'ส่วนมือ',
|
||||
slotBodyShort: 'ลำตัว',
|
||||
slotFeetShort: 'รองเท้า',
|
||||
slotSphereShort: 'ลูกแก้ว',
|
||||
slotRopeShort: 'สร้อย',
|
||||
traceFallbackA2: 'ร่องรอย A2',
|
||||
traceFallbackA4: 'ร่องรอย A4',
|
||||
traceFallbackA6: 'ร่องรอย A6',
|
||||
printSavePdf: 'พิมพ์ / บันทึกเป็น PDF หรือรูปภาพ',
|
||||
printClose: 'ปิดหน้าต่าง',
|
||||
exRankNoData: 'ไม่มีข้อมูล Ex-Rank',
|
||||
exRankEnabled: 'เปิดใช้งานแล้ว',
|
||||
exRankDisabled: 'ปิดการใช้งาน',
|
||||
jsonStatusLoading: 'กำลังโหลด {file}...',
|
||||
jsonStatusMemory: '{file} (โหลดจากหน่วยความจำ/แคช)',
|
||||
jsonStatusServer: '{file} (โหลดจากเซิร์ฟเวอร์)',
|
||||
jsonStatusBrowserCache: '{file} (โหลดจากแคชของเบราว์เซอร์)',
|
||||
jsonStatusCache: '{file} (โหลดจากแคช)',
|
||||
jsonStatusDump: '{file} (โครงสร้าง Firefly dump)',
|
||||
jsonOfflineNoCache: 'Offline: ไม่มีแคช สามารถอัปโหลดหรือพิมพ์ JSON เองได้',
|
||||
jsonOfflinePaste: 'Offline: สามารถพิมพ์หรือวาง JSON เองได้',
|
||||
jsonCacheUnavailable: 'ไม่สามารถตรวจสอบได้',
|
||||
copyCardName: 'ชื่อ',
|
||||
copyCardPath: 'สาย',
|
||||
copyCardElement: 'ธาตุ',
|
||||
copyCardLevel: 'เลเวล',
|
||||
copyCardEidolon: 'Eidolon',
|
||||
copyCardLightCone: 'กรวยแสง',
|
||||
copyCardGenerated: 'สร้างโดย GaC-Srtool'
|
||||
},
|
||||
en: {
|
||||
relicSlotNotEquipped: 'Not equipped',
|
||||
relicSlotCreateHint: '+ Click to choose or create (recommended: {stat})',
|
||||
relicSlotUnequip: 'Unequip this piece',
|
||||
relicSubstatFallback: '4 substats (click to customize)',
|
||||
relicSetNoBonus: 'No set effect active (equip at least 2 or 4 pieces of the same set)',
|
||||
relicSet4Fallback: '4-piece set effect active',
|
||||
relicSet2Fallback: '2-piece set effect active',
|
||||
overviewRelicCount: '{count}/6 equipped (no set bonus yet)',
|
||||
relicPickerEditTitle: '🔧 Edit relic: {set} ({slot}) - {name}',
|
||||
relicPickerEditSubtitle: 'Customize the set, main stat and substats of this relic (#{id})',
|
||||
relicPickerEditing: '✏️ Editing relic #{id}',
|
||||
relicPickerSaveChanges: '💾 Save substat changes',
|
||||
relicPickerEquipNow: '⚡ Equip this piece now (+15 5★ with selected stats)',
|
||||
relicPickerInInventory: 'In inventory',
|
||||
relicPickerEditThis: '✏️ Edit this piece',
|
||||
relicPickerEditThisTitle: 'Load this piece to edit its substats',
|
||||
relicPickerRecommended: 'Recommended 4 substats - customize them below',
|
||||
substatMainConflict: ' (matches main stat)',
|
||||
substatAlreadyChosen: ' (already chosen)',
|
||||
substatSlotLabel: 'Substat {n}',
|
||||
substatRolls: '+{value} ({count} rolls)',
|
||||
substatDecreaseRoll: 'Decrease rolls',
|
||||
substatIncreaseRoll: 'Increase rolls',
|
||||
relicEmptyInventoryTitle: 'No relics for this slot in the inventory',
|
||||
relicEmptyInventoryHint: 'Switch to the "⚡ Equip / Create recommended" tab to create a new piece.',
|
||||
relicEquippedNow: 'Currently equipped',
|
||||
relicWornBy: 'Equipped by: {name}',
|
||||
relicFreeInInventory: 'Free in inventory',
|
||||
relicInUse: 'In use',
|
||||
relicSelectThis: 'Select this piece',
|
||||
lcNoneEquipped: 'No Light Cone equipped',
|
||||
lcSelectPrompt: 'Choose a Light Cone to equip',
|
||||
lcSamePathGroup: '⭐ {path} Light Cones',
|
||||
lcOtherPathGroup: '🗡️ Other paths',
|
||||
lcNoResults: 'No Light Cones match your search',
|
||||
lcEquippedBadge: 'Equipped',
|
||||
lcSignatureBadge: 'Signature',
|
||||
lcSelectTitle: '🗡️ Choose a Light Cone: {name}',
|
||||
eqSkillFallbackTitle: 'Light Cone passive',
|
||||
eqSkillFallbackDesc: 'Boosts combat power and reinforces the character path mechanics when equipped',
|
||||
eqEquippedByLabel: 'Equipped by:',
|
||||
eqSaveWearer: '💾 Save equipment',
|
||||
eqDelete: '🗑️ Delete',
|
||||
eqDeleteTitle: 'Remove from inventory',
|
||||
eqSlotN: 'Piece {n}',
|
||||
eqMainStatLabel: 'Main stat',
|
||||
eqSubstatsLabel: 'Substats:',
|
||||
eqNoSubstats: 'No substat data',
|
||||
eqEditSubstats: '🔧 Edit substats',
|
||||
eqSet2Fallback: 'Improves performance based on the set',
|
||||
eqSet4Fallback: 'Unlocks advanced combat effects',
|
||||
showcaseActiveSets: 'Active Set Bonuses',
|
||||
showcaseEquippedRelics: 'Equipped Relics (+15)',
|
||||
showcaseBuildTag: 'Build Overview',
|
||||
showcaseRankLevel: 'S{rank} • Lv. {level}/80',
|
||||
consolePreviewHint: 'Type / to choose a command',
|
||||
consoleDescriptionHint: 'Select a command to see an example and its arguments',
|
||||
consoleArgumentHint: '↑ ↓ navigate · Tab fill selected · Esc close',
|
||||
consoleKeyboardHint: '↑ ↓ navigate · Tab fill · Shift+Tab previous · Esc close · Enter send',
|
||||
cameraHint: 'Choose a mode, then press Tab in game to toggle the camera',
|
||||
cameraMode: 'Camera mode',
|
||||
cameraFaceCamDistance: 'FaceCam distance',
|
||||
cameraSave: 'Save camera',
|
||||
cameraFaceCamExperimental: 'FaceCam (experimental)',
|
||||
cameraDistanceError: 'FaceCam distance must be between 0.5 and 5',
|
||||
cameraSaved: 'Camera saved',
|
||||
lcPassiveFallback: 'Increases basic stats following the <strong>{path}</strong> path and boosts damage and support performance by <strong>{value}%</strong> in battle',
|
||||
slotHeadShort: 'Head',
|
||||
slotHandsShort: 'Hands',
|
||||
slotBodyShort: 'Body',
|
||||
slotFeetShort: 'Feet',
|
||||
slotSphereShort: 'Planar Sphere',
|
||||
slotRopeShort: 'Link Rope',
|
||||
traceFallbackA2: 'Trace A2',
|
||||
traceFallbackA4: 'Trace A4',
|
||||
traceFallbackA6: 'Trace A6',
|
||||
printSavePdf: 'Print / save as PDF or image',
|
||||
printClose: 'Close window',
|
||||
exRankNoData: 'No Ex-Rank data',
|
||||
exRankEnabled: 'Enabled',
|
||||
exRankDisabled: 'Disabled',
|
||||
jsonStatusLoading: 'Loading {file}...',
|
||||
jsonStatusMemory: '{file} (from memory/cache)',
|
||||
jsonStatusServer: '{file} (from server)',
|
||||
jsonStatusBrowserCache: '{file} (from browser cache)',
|
||||
jsonStatusCache: '{file} (from cache)',
|
||||
jsonStatusDump: '{file} (Firefly dump structure)',
|
||||
jsonOfflineNoCache: 'Offline: no cache. You can upload or paste JSON.',
|
||||
jsonOfflinePaste: 'Offline: you can paste or type JSON.',
|
||||
jsonCacheUnavailable: 'Unavailable',
|
||||
copyCardName: 'Name',
|
||||
copyCardPath: 'Path',
|
||||
copyCardElement: 'Element',
|
||||
copyCardLevel: 'Level',
|
||||
copyCardEidolon: 'Eidolon',
|
||||
copyCardLightCone: 'Light Cone',
|
||||
copyCardGenerated: 'Generated via GaC-Srtool'
|
||||
}
|
||||
};
|
||||
for (const [language, additions] of Object.entries(MODAL_AND_TOOL_TRANSLATIONS)) {
|
||||
if (TRANSLATIONS[language]) Object.assign(TRANSLATIONS[language], additions);
|
||||
}
|
||||
|
||||
let currentLang = 'th';
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
currentLang = localStorage.getItem('gac_srtool_lang') || 'th';
|
||||
|
||||
Reference in new issue
Block a user