refactor: 重构多语言和走马灯

- 删除 src/locales/en-US/common.ts 文件中的所有国际化文案
- 删除 src/locales/id-ID/common.ts 文件中的所有国际化文案
- 移除与游戏、认证、UI组件相关的多语言配置项
- 清理导航、游戏大厅、语言选择等界面的翻译内容
- 移除登录注册表单、验证规则等认证相关文案
- 删除支付、提款、钱包记录等财务功能翻译项
This commit is contained in:
JiaJun
2026-06-02 14:31:49 +08:00
parent 901ad1c30b
commit 3efcb3bba6
10 changed files with 155 additions and 61 deletions

View File

@@ -35,6 +35,8 @@
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"dayjs": "^1.11.20", "dayjs": "^1.11.20",
"embla-carousel-auto-scroll": "^8.6.0",
"embla-carousel-react": "^8.6.0",
"i18next": "^26.0.5", "i18next": "^26.0.5",
"ky": "^2.0.1", "ky": "^2.0.1",
"lottie-web": "^5.13.0", "lottie-web": "^5.13.0",

40
pnpm-lock.yaml generated
View File

@@ -38,6 +38,12 @@ importers:
dayjs: dayjs:
specifier: ^1.11.20 specifier: ^1.11.20
version: 1.11.20 version: 1.11.20
embla-carousel-auto-scroll:
specifier: ^8.6.0
version: 8.6.0(embla-carousel@8.6.0)
embla-carousel-react:
specifier: ^8.6.0
version: 8.6.0(react@19.2.5)
i18next: i18next:
specifier: ^26.0.5 specifier: ^26.0.5
version: 26.0.5(typescript@6.0.2) version: 26.0.5(typescript@6.0.2)
@@ -2234,6 +2240,24 @@ packages:
electron-to-chromium@1.5.338: electron-to-chromium@1.5.338:
resolution: {integrity: sha512-KVQQ3xko9/coDX3qXLUEEbqkKT8L+1DyAovrtu0Khtrt9wjSZ+7CZV4GVzxFy9Oe1NbrIU1oVXCwHJruIA1PNg==} resolution: {integrity: sha512-KVQQ3xko9/coDX3qXLUEEbqkKT8L+1DyAovrtu0Khtrt9wjSZ+7CZV4GVzxFy9Oe1NbrIU1oVXCwHJruIA1PNg==}
embla-carousel-auto-scroll@8.6.0:
resolution: {integrity: sha512-WT9fWhNXFpbQ6kP+aS07oF5IHYLZ1Dx4DkwgCY8Hv2ZyYd2KMCPfMV1q/cA3wFGuLO7GMgKiySLX90/pQkcOdQ==}
peerDependencies:
embla-carousel: 8.6.0
embla-carousel-react@8.6.0:
resolution: {integrity: sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==}
peerDependencies:
react: ^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
embla-carousel-reactive-utils@8.6.0:
resolution: {integrity: sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A==}
peerDependencies:
embla-carousel: 8.6.0
embla-carousel@8.6.0:
resolution: {integrity: sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==}
emoji-regex@10.6.0: emoji-regex@10.6.0:
resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
@@ -5888,6 +5912,22 @@ snapshots:
electron-to-chromium@1.5.338: {} electron-to-chromium@1.5.338: {}
embla-carousel-auto-scroll@8.6.0(embla-carousel@8.6.0):
dependencies:
embla-carousel: 8.6.0
embla-carousel-react@8.6.0(react@19.2.5):
dependencies:
embla-carousel: 8.6.0
embla-carousel-reactive-utils: 8.6.0(embla-carousel@8.6.0)
react: 19.2.5
embla-carousel-reactive-utils@8.6.0(embla-carousel@8.6.0):
dependencies:
embla-carousel: 8.6.0
embla-carousel@8.6.0: {}
emoji-regex@10.6.0: {} emoji-regex@10.6.0: {}
emoji-regex@8.0.0: {} emoji-regex@8.0.0: {}

View File

@@ -16,8 +16,8 @@ import { useGameRoundStore } from '@/store/game'
const SETTLEMENT_REVEAL_RANDOM_DURATION_MS = 3_200 const SETTLEMENT_REVEAL_RANDOM_DURATION_MS = 3_200
const SETTLEMENT_REVEAL_SETTLE_DURATION_MS = 800 const SETTLEMENT_REVEAL_SETTLE_DURATION_MS = 800
const SETTLEMENT_REVEAL_RESULT_HOLD_MS = 1_000 const SETTLEMENT_REVEAL_RESULT_HOLD_MS = 1_000
const SETTLEMENT_REVEAL_MIN_STEP_MS = 90 const SETTLEMENT_REVEAL_MIN_STEP_MS = 180
const SETTLEMENT_REVEAL_MAX_STEP_MS = 480 const SETTLEMENT_REVEAL_MAX_STEP_MS = 960
function getRandomAnimalId(ids: number[], currentId: number | null) { function getRandomAnimalId(ids: number[], currentId: number | null) {
if (ids.length === 0) { if (ids.length === 0) {
@@ -436,12 +436,12 @@ export function DesktopAnimal({
<div <div
aria-hidden="true" aria-hidden="true"
className={cn( className={cn(
'pointer-events-none absolute z-40 transition-[height,transform,width]', 'pointer-events-none absolute z-40 transform-gpu transition-[height,transform,width]',
prefersReducedMotion prefersReducedMotion
? 'duration-0' ? 'duration-0'
: isRevealSettlingResult : isRevealSettlingResult
? 'duration-[800ms] ease-[cubic-bezier(0.16,1,0.3,1)]' ? 'duration-[800ms] ease-[cubic-bezier(0.16,1,0.3,1)]'
: 'duration-75 ease-linear', : 'duration-[160ms] ease-[cubic-bezier(0.22,1,0.36,1)]',
)} )}
style={{ style={{
height: revealFrame.height, height: revealFrame.height,

View File

@@ -1,3 +1,6 @@
import AutoScroll from 'embla-carousel-auto-scroll'
import useEmblaCarousel from 'embla-carousel-react'
import { useEffect, useMemo } from 'react'
import broadcast from '@/assets/system/broadcast.webp' import broadcast from '@/assets/system/broadcast.webp'
import { SmartImage } from '@/components/smart-image.tsx' import { SmartImage } from '@/components/smart-image.tsx'
import { useGameSessionStore } from '@/store/game' import { useGameSessionStore } from '@/store/game'
@@ -5,6 +8,21 @@ import { useGameSessionStore } from '@/store/game'
const winAmountFormatter = new Intl.NumberFormat('en-US', { const winAmountFormatter = new Intl.NumberFormat('en-US', {
maximumFractionDigits: 6, maximumFractionDigits: 6,
}) })
function getDesktopTitleCarouselCycleCount(titleCount: number) {
if (titleCount >= 8) {
return 2
}
if (titleCount >= 5) {
return 3
}
if (titleCount >= 3) {
return 5
}
return 8
}
function formatWinAmount(value: string) { function formatWinAmount(value: string) {
const amount = Number(value) const amount = Number(value)
@@ -16,22 +34,77 @@ export function DesktopTitle() {
const jackpotBroadcasts = useGameSessionStore( const jackpotBroadcasts = useGameSessionStore(
(state) => state.jackpotBroadcasts, (state) => state.jackpotBroadcasts,
) )
const titles = const hasBroadcasts = jackpotBroadcasts.length > 0
jackpotBroadcasts.length > 0 const titles = useMemo(
? jackpotBroadcasts.map((broadcast) => ({ () =>
id: broadcast.id, hasBroadcasts
message: `Player ${broadcast.nickname} won ${formatWinAmount( ? jackpotBroadcasts.map((broadcast) => ({
broadcast.totalWin, id: broadcast.id,
)}`, message: `Player ${broadcast.nickname} won ${formatWinAmount(
})) broadcast.totalWin,
: [{ id: 'empty', message: '' }] )}`,
const marqueeTitles = }))
jackpotBroadcasts.length > 0 : [{ id: 'empty', message: '' }],
? [ [hasBroadcasts, jackpotBroadcasts],
...titles.map((title) => ({ ...title, id: `${title.id}:first` })), )
...titles.map((title) => ({ ...title, id: `${title.id}:second` })), const carouselTitles = useMemo(
] () =>
: titles hasBroadcasts
? Array.from(
{ length: getDesktopTitleCarouselCycleCount(titles.length) },
(_, cycleIndex) =>
titles.map((title) => ({
...title,
cycleIndex,
id: `${title.id}:cycle-${cycleIndex}`,
})),
).flat()
: titles.map((title) => ({ ...title, cycleIndex: 0 })),
[hasBroadcasts, titles],
)
const carouselTitleCount = carouselTitles.length
const autoScrollPlugin = useMemo(
() =>
AutoScroll({
playOnInit: hasBroadcasts,
speed: 0.7,
startDelay: 0,
stopOnFocusIn: false,
stopOnInteraction: false,
stopOnMouseEnter: false,
}),
[hasBroadcasts],
)
const [emblaRef, emblaApi] = useEmblaCarousel(
{
align: 'start',
dragFree: true,
loop: hasBroadcasts,
watchDrag: false,
},
[autoScrollPlugin],
)
useEffect(() => {
if (!emblaApi) {
return
}
if (carouselTitleCount === 0) {
return
}
emblaApi.reInit()
const autoScroll = emblaApi.plugins().autoScroll
if (!hasBroadcasts) {
autoScroll?.stop()
return
}
autoScroll?.reset()
autoScroll?.play()
}, [emblaApi, hasBroadcasts, carouselTitleCount])
return ( return (
<section className="common-neon-inset text-design-16 w-full flex h-design-65 items-center gap-design-10 !px-design-20 overflow-hidden"> <section className="common-neon-inset text-design-16 w-full flex h-design-65 items-center gap-design-10 !px-design-20 overflow-hidden">
@@ -41,21 +114,18 @@ export function DesktopTitle() {
src={broadcast} src={broadcast}
/> />
<div className="relative h-design-28 min-w-0 flex-1 overflow-hidden"> <div className="relative h-design-28 min-w-0 flex-1 overflow-hidden">
<div <div className="h-full overflow-hidden" ref={emblaRef}>
className={ <div className="flex h-full items-center gap-design-80">
jackpotBroadcasts.length > 0 {carouselTitles.map((title) => (
? 'desktop-title-horizontal-marquee' <div
: '' aria-hidden={title.cycleIndex > 0}
} className="flex h-design-28 min-w-0 shrink-0 items-center whitespace-nowrap !text-[#FF970F]"
> key={title.id}
{marqueeTitles.map((title) => ( >
<div {title.message}
className="flex h-design-28 shrink-0 items-center whitespace-nowrap !text-[#FF970F]" </div>
key={title.id} ))}
> </div>
{title.message}
</div>
))}
</div> </div>
</div> </div>
</section> </section>

View File

@@ -2,10 +2,10 @@ import i18n from 'i18next'
import { initReactI18next } from 'react-i18next' import { initReactI18next } from 'react-i18next'
import { DEFAULT_APP_LANGUAGE, SUPPORTED_LANGUAGES } from '@/constants/system' import { DEFAULT_APP_LANGUAGE, SUPPORTED_LANGUAGES } from '@/constants/system'
import enUSCommon from '@/locales/en-US/common' import enUS from '@/locales/en-US'
import idIDCommon from '@/locales/id-ID/common' import idID from '@/locales/id-ID'
import msMYCommon from '@/locales/ms-MY/common' import msMY from '@/locales/ms-MY'
import zhCNCommon from '@/locales/zh-CN/common' import zhCN from '@/locales/zh-CN'
import { getStoredAppLanguage, setStoredAppLanguage } from '@/store/auth' import { getStoredAppLanguage, setStoredAppLanguage } from '@/store/auth'
export type AppLanguage = (typeof SUPPORTED_LANGUAGES)[number] export type AppLanguage = (typeof SUPPORTED_LANGUAGES)[number]
@@ -53,16 +53,16 @@ void i18n.use(initReactI18next).init({
}, },
resources: { resources: {
'zh-CN': { 'zh-CN': {
common: zhCNCommon, common: zhCN,
}, },
'en-US': { 'en-US': {
common: enUSCommon, common: enUS,
}, },
'ms-MY': { 'ms-MY': {
common: msMYCommon, common: msMY,
}, },
'id-ID': { 'id-ID': {
common: idIDCommon, common: idID,
}, },
}, },
defaultNS: 'common', defaultNS: 'common',

View File

@@ -525,24 +525,6 @@
display: none; display: none;
} }
.desktop-title-horizontal-marquee {
display: flex;
width: max-content;
gap: calc(var(--design-unit) * 80);
animation: desktop-title-marquee-x 16s linear infinite;
will-change: transform;
}
@keyframes desktop-title-marquee-x {
from {
transform: translateX(0);
}
to {
transform: translateX(-50%);
}
}
@property --gold-angle { @property --gold-angle {
syntax: "<angle>"; syntax: "<angle>";
inherits: false; inherits: false;