fix ja language, instead fetch by axios

This commit is contained in:
2025-05-04 19:26:14 +07:00
parent fbdfa6d557
commit 08df765d25
12 changed files with 263 additions and 91 deletions

View File

@@ -4,6 +4,7 @@
"": {
"name": "firefly-analytics",
"dependencies": {
"axios": "^1.9.0",
"chart.js": "^4.4.9",
"chartjs-plugin-datalabels": "^2.2.0",
"framer-motion": "^12.7.4",
@@ -316,10 +317,14 @@
"async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="],
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
"axe-core": ["axe-core@4.10.3", "", {}, "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg=="],
"axios": ["axios@1.9.0", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg=="],
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
@@ -360,6 +365,8 @@
"color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="],
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
@@ -386,6 +393,8 @@
"define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
"detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
"diff-sequences": ["diff-sequences@29.6.3", "", {}, "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q=="],
@@ -476,8 +485,12 @@
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
"follow-redirects": ["follow-redirects@1.15.9", "", {}, "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="],
"for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
"form-data": ["form-data@4.0.2", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" } }, "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w=="],
"framer-motion": ["framer-motion@12.9.4", "", { "dependencies": { "motion-dom": "^12.9.4", "motion-utils": "^12.9.4", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-yaeGDmGQ3eCQEwZ95/pRQMaSh/Q4E2CK6JYOclG/PdjyQad0MULJ+JFVV8911Fl5a6tF6o0wgW8Dpl5Qx4Adjg=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
@@ -658,6 +671,10 @@
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
@@ -728,6 +745,8 @@
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],

108
messages/cn.json Normal file
View File

@@ -0,0 +1,108 @@
{
"TabTitle": {
"title": "Firefly 分析器",
"description": "Veritas 分析工具"
},
"DataAnalysisPage": {
"useSkill": "使用技能",
"totalDamage": "总伤害",
"damagePerAV": "每行动值伤害",
"totalAV": "总行动值",
"skillType": "技能类型",
"skillName": "技能名称",
"actionValue": "行动值",
"character": "角色",
"id": "ID",
"path": "命途",
"rarity": "稀有度",
"damageByActionValue": "此行动值的伤害",
"element": "属性",
"totalTurn": "总回合",
"technique": "密技",
"talent": "天赋",
"basic": "普攻",
"skill": "战技",
"ultimate": "终结技",
"servant": "忆灵",
"skillDamageBreakdown": "技能类别",
"skillUsageDistribution": "技能使用分布",
"damageOverTime": "累计伤害",
"damage": "伤害",
"cumulativeDamage": "累计伤害量",
"characterInformation": "角色信息",
"turnDetail": "轮次详情",
"damageDetails": "伤害详情",
"cycleCount": "轮次",
"chartInfo": "图表信息",
"actionBar": "行动轴",
"lineupInfo": "阵容信息",
"loadData": "加载战斗数据",
"exportData": "导出战斗数据",
"connectSetting": "连接设置",
"connected": "已连接",
"unconnected": "未连接",
"socketConnection": "socket连接",
"connectionType": "连接类型",
"status": "状态",
"connect": "连接",
"checkGameConnect": "检查游戏连接",
"other": "其他",
"host": "主机",
"port": "端口",
"hostPlaceHolder": "输入主机地址",
"portPlaceHolder": "输入端口号",
"noDamageDetail": "没有可用的伤害详情",
"noCharactersInLineup": "队伍中没有角色",
"noTurns": "尚未有回合",
"type": "类型 ",
"warrior": "毁灭",
"knight": "存护",
"mage": "智识",
"priest": "丰饶",
"rouge": "巡猎",
"shaman": "同协",
"warlock": "虚无",
"memory": "记忆",
"fire": "火",
"ice": "冰",
"imaginary": "虚数",
"physical": "物理",
"quantum": "量子",
"thunder": "雷",
"wind": "风",
"cycle": "回合",
"wave": "波次",
"hp": "生命值",
"atk": "攻击力",
"speed": "速度",
"critRate": "暴击率",
"critDmg": "暴击伤害",
"breakEffect": "击破特攻",
"effectRes": "效果抵抗",
"energyRegenerationRate": "能量回复效率",
"effectHitRate": "效果命中",
"outgoingHealingBoost": "治疗量加成",
"fireDmgBoost": "火属性伤害提高",
"iceDmgBoost": "冰属性伤害提高",
"imaginaryDmgBoost": "虚数属性伤害提高",
"physicalDmgBoost": "物理属性伤害提高",
"quantumDmgBoost": "量子属性伤害提高",
"thunderDmgBoost": "雷属性伤害提高",
"windDmgBoost": "风属性伤害提高",
"pursued": "附加伤害",
"true damage": "真实伤害",
"follow-up": "追加攻击",
"elemental damage": "击破与超击破伤害",
"dot": "持续伤害 ",
"damagePerCycle": "每轮伤害",
"damagePerCycleAndWave": "每轮/波伤害",
"damagePerWave": "每波伤害",
"lastTurn": "最后一回合",
"qte": "QTE技能",
"mazenormal": "普通迷宫",
"level": "等级",
"relics": "遗器",
"eidolons": "星魂",
"lightcones": "光锥"
}
}

View File

@@ -29,14 +29,7 @@ const nextConfig: NextConfig = {
eslint: {
ignoreDuringBuilds: true,
},
async rewrites() {
return [
{
source: '/api/hakushin',
destination: 'https://api.hakush.in/hsr/data/character.json',
},
];
},
};
export default withNextIntl(nextConfig);

View File

@@ -9,6 +9,7 @@
"lint": "next lint"
},
"dependencies": {
"axios": "^1.9.0",
"chart.js": "^4.4.9",
"chartjs-plugin-datalabels": "^2.2.0",
"framer-motion": "^12.7.4",

View File

@@ -9,6 +9,7 @@ import { useTranslations } from "next-intl";
import { motion } from "framer-motion";
import { getNameChar } from "@/helper";
import Image from "next/image";
import NameAvatar from "../nameAvatar";
export default function ActionBar() {
const [selectTurn, setSelectTurn] = useState<SkillBattleInfo | null>(null);
@@ -128,8 +129,11 @@ export default function ActionBar() {
/>
</div>
</div>
<div className="text-base-content text-center text-sm mt-1 font-medium">{getNameChar(locale, data)}</div>
<NameAvatar
locale={locale}
text={getNameChar(locale, data)}
className="text-base-content text-center text-sm mt-1 font-medium"
/>
</div>
<div className="grid grid-cols-1 justify-center gap-2 py-2 w-full">
<div className="bg-local text-primary text-xs max-w-full">
@@ -180,14 +184,18 @@ export default function ActionBar() {
<h4 className="text-lg font-semibold mb-2 text-pink-500">{transI18n("characterInformation")}</h4>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 items-center">
<div className="flex flex-col space-y-2">
<p className="flex items-center gap-2">
<div className="flex items-center gap-2">
<span className="font-medium text-base-content/70">{transI18n("id")}:</span>
<span className="font-bold">{selectAvatar.id}</span>
</p>
<p className="flex items-center gap-2">
</div>
<div className="flex items-center gap-2">
<span className="font-medium text-base-content/70">{transI18n("character")}:</span>
<span className="font-bold">{getNameChar(locale, selectAvatar)}</span>
</p>
<NameAvatar
locale={locale}
text={getNameChar(locale, selectAvatar)}
className="font-bold"
/>
</div>
</div>
<div className="flex justify-center items-center">
<Image

View File

@@ -3,16 +3,12 @@
import { getNameChar } from '@/helper';
import useLocaleStore from '@/stores/localeStore';
import { AvatarHakushiType } from '@/types';
import NameAvatar from '../nameAvatar';
interface CharacterCardProps {
data: AvatarHakushiType
}
export function parseRuby(text: string): string {
return text.replace(/\{RUBY_B#(.*?)\}(.*?)\{RUBY_E#\}/g, (_, furigana, kanji) => {
return `<ruby>${kanji}<rt>${furigana}</rt></ruby>`;
});
}
export default function CharacterCard({ data }: CharacterCardProps) {
@@ -21,8 +17,7 @@ export default function CharacterCard({ data }: CharacterCardProps) {
return (
<li className="z-10 flex flex-col w-28 items-center p-1 rounded-md shadow-lg bg-gradient-to-b from-customStart to-customEnd transform transition-transform duration-300 hover:scale-105 m-1">
<div
className={`w-[80px] rounded-md p-[2px] bg-gradient-to-br ${
data.rank === "CombatPowerAvatarRarityType5"
className={`w-[80px] rounded-md p-[2px] bg-gradient-to-br ${data.rank === "CombatPowerAvatarRarityType5"
? "from-yellow-400 via-yellow-300 to-yellow-500"
: "from-purple-300 via-purple-200 to-purple-400"
}`}
@@ -50,16 +45,11 @@ export default function CharacterCard({ data }: CharacterCardProps) {
</div>
</div>
{locale === "jp" ? (
<div
<NameAvatar
locale={locale}
text={text}
className="mt-2 text-center text-base font-normal leading-tight"
dangerouslySetInnerHTML={{ __html: parseRuby(text) }}
/>
) : (
<div className="mt-2 text-center text-base font-normal leading-tight">
{text}
</div>
)}
</li>
);

View File

@@ -32,7 +32,7 @@ export default function Header() {
useEffect(() => {
console.log(navigator.language.slice(0, 2))
const cookieLocale = document.cookie.split("; ")
.find((row) => row.startsWith("MYNEXTAPP_LOCALE"))
?.split("=")[1];

View File

@@ -14,10 +14,11 @@ import { DamageLineForOne } from "../chart/damageLineForOne";
import { DamagePerCycleForOne } from "../chart/damagePerCycleForOne";
import { useCalcTotalDmgAvatar, useCalcTotalTurnAvatar } from "@/hooks/useCalcAvatarData";
import Image from "next/image";
import NameAvatar from "../nameAvatar";
// import ShowCaseInfo from "../card/showCaseCard";
export default function LineupBar() {
const [selectedCharacter, setSelectedCharacter] = useState<AvatarHakushiType | null>(null);
const [selectedCharacter, setSelectedCharacter] = useState<AvatarHakushiType | undefined>(undefined);
const [isModalOpen, setIsModalOpen] = useState(false);
const transI18n = useTranslations("DataAnalysisPage");
@@ -44,7 +45,7 @@ export default function LineupBar() {
// Close modal handler
const handleCloseModal = (modalId: string) => {
setIsModalOpen(false);
setSelectedCharacter(null);
setSelectedCharacter(undefined);
const modal = document.getElementById(modalId) as HTMLDialogElement | null;
if (modal) {
modal.close()
@@ -80,7 +81,16 @@ export default function LineupBar() {
<path strokeLinecap="round" strokeLinejoin="round" d="M17.982 18.725A7.488 7.488 0 0 0 12 15.75a7.488 7.488 0 0 0-5.982 2.975m11.963 0a9 9 0 1 0-11.963 0m11.963 0A8.966 8.966 0 0 1 12 21a8.966 8.966 0 0 1-5.982-2.275M15 9.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
<span className="text-sm truncate">{transI18n("lastTurn")}: {getNameChar(locale, listAvatar.find(it => it.id === turnHistory.findLast(i => i?.avatarId)?.avatarId?.toString()))}</span>
<span className="text-sm truncate flex flex-row">
<div>
{transI18n("lastTurn")}:
</div>
<NameAvatar
locale={locale}
text={getNameChar(locale, listAvatar.find(it => it.id === turnHistory.findLast(i => i?.avatarId)?.avatarId?.toString()))}
/>
</span>
</div>
</div>
@@ -132,9 +142,12 @@ export default function LineupBar() {
</div>
<div className="border-b border-purple-500/30 px-6 py-4 mb-4">
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-400">
{selectedCharacter ? getNameChar(locale, selectedCharacter).toUpperCase() : ""}
</h3>
<NameAvatar
locale={locale}
text={getNameChar(locale, selectedCharacter).toUpperCase()}
className={"font-bold text-2xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-400"}
/>
</div>
{selectedCharacter && (

View File

@@ -0,0 +1,16 @@
import { parseRuby } from "@/helper";
interface TextProps {
text: string;
locale: string;
className?: string;
}
export default function NameAvatar({ text, locale, className }: TextProps) {
if (locale === "ja") {
return <div className={className} dangerouslySetInnerHTML={{ __html: parseRuby(text) }} />;
}
return <div className={className}>{text}</div>;
}

View File

@@ -19,6 +19,12 @@ export function getNameChar(locale: string, data: AvatarHakushiType | undefined)
} else if (Number(data.id) > 8000) {
text = `Male ${data.damageType} MC`
}
return text
}
export function parseRuby(text: string): string {
const rubyRegex = /\{RUBY_B#(.*?)\}(.*?)\{RUBY_E#\}/gs;
return text.replace(rubyRegex, (_match, furigana, kanji) => {
return `<ruby>${kanji}<rt>${furigana}</rt></ruby>`;
});
}

View File

@@ -1,6 +1,6 @@
import useSocketStore from "@/stores/socketSettingStore";
import { AvatarHakushiType, AvatarHakushiRawType } from "@/types/avatar";
import axios from 'axios';
export async function checkConnectTcpApi(): Promise<boolean> {
const { host, port, connectionType } = useSocketStore.getState()
@@ -23,22 +23,27 @@ export async function checkConnectTcpApi(): Promise<boolean> {
}
export async function getCharacterListApi(): Promise<AvatarHakushiType[]> {
const res = await fetch('/api/hakushin', {
method: 'GET',
try {
const res = await axios.get<Record<string, AvatarHakushiRawType>>(
'https://api.hakush.in/hsr/data/character.json',
{
headers: {
'Content-Type': 'application/json',
},
});
if (!res.ok) {
console.log(`Error ${res.status}: ${res.statusText}`);
return [];
}
);
const data: Map<string, AvatarHakushiRawType> = new Map(Object.entries(await res.json()));
const data = new Map(Object.entries(res.data));
return Array.from(data.entries()).map(([id, it]) => convertAvatar(id, it));
} catch (error: unknown) {
if (axios.isAxiosError(error)) {
console.log(`Error: ${error.response?.status} - ${error.message}`);
} else {
console.log(`Unexpected error: ${String(error)}`);
}
return [];
}
}

View File

@@ -1,12 +1,15 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"target": "ES2018",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
@@ -19,9 +22,19 @@
}
],
"paths": {
"@/*": ["./src/*"]
}
"@/*": [
"./src/*"
]
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
"esModuleInterop": true
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}