refactor: 重构多语言和走马灯
- 删除 src/locales/en-US/common.ts 文件中的所有国际化文案 - 删除 src/locales/id-ID/common.ts 文件中的所有国际化文案 - 移除与游戏、认证、UI组件相关的多语言配置项 - 清理导航、游戏大厅、语言选择等界面的翻译内容 - 移除登录注册表单、验证规则等认证相关文案 - 删除支付、提款、钱包记录等财务功能翻译项
This commit is contained in:
@@ -35,6 +35,8 @@
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.20",
|
||||
"embla-carousel-auto-scroll": "^8.6.0",
|
||||
"embla-carousel-react": "^8.6.0",
|
||||
"i18next": "^26.0.5",
|
||||
"ky": "^2.0.1",
|
||||
"lottie-web": "^5.13.0",
|
||||
|
||||
40
pnpm-lock.yaml
generated
40
pnpm-lock.yaml
generated
@@ -38,6 +38,12 @@ importers:
|
||||
dayjs:
|
||||
specifier: ^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:
|
||||
specifier: ^26.0.5
|
||||
version: 26.0.5(typescript@6.0.2)
|
||||
@@ -2234,6 +2240,24 @@ packages:
|
||||
electron-to-chromium@1.5.338:
|
||||
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:
|
||||
resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
|
||||
|
||||
@@ -5888,6 +5912,22 @@ snapshots:
|
||||
|
||||
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@8.0.0: {}
|
||||
|
||||
@@ -16,8 +16,8 @@ import { useGameRoundStore } from '@/store/game'
|
||||
const SETTLEMENT_REVEAL_RANDOM_DURATION_MS = 3_200
|
||||
const SETTLEMENT_REVEAL_SETTLE_DURATION_MS = 800
|
||||
const SETTLEMENT_REVEAL_RESULT_HOLD_MS = 1_000
|
||||
const SETTLEMENT_REVEAL_MIN_STEP_MS = 90
|
||||
const SETTLEMENT_REVEAL_MAX_STEP_MS = 480
|
||||
const SETTLEMENT_REVEAL_MIN_STEP_MS = 180
|
||||
const SETTLEMENT_REVEAL_MAX_STEP_MS = 960
|
||||
|
||||
function getRandomAnimalId(ids: number[], currentId: number | null) {
|
||||
if (ids.length === 0) {
|
||||
@@ -436,12 +436,12 @@ export function DesktopAnimal({
|
||||
<div
|
||||
aria-hidden="true"
|
||||
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
|
||||
? 'duration-0'
|
||||
: isRevealSettlingResult
|
||||
? '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={{
|
||||
height: revealFrame.height,
|
||||
|
||||
@@ -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 { SmartImage } from '@/components/smart-image.tsx'
|
||||
import { useGameSessionStore } from '@/store/game'
|
||||
@@ -5,6 +8,21 @@ import { useGameSessionStore } from '@/store/game'
|
||||
const winAmountFormatter = new Intl.NumberFormat('en-US', {
|
||||
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) {
|
||||
const amount = Number(value)
|
||||
@@ -16,22 +34,77 @@ export function DesktopTitle() {
|
||||
const jackpotBroadcasts = useGameSessionStore(
|
||||
(state) => state.jackpotBroadcasts,
|
||||
)
|
||||
const titles =
|
||||
jackpotBroadcasts.length > 0
|
||||
? jackpotBroadcasts.map((broadcast) => ({
|
||||
id: broadcast.id,
|
||||
message: `Player ${broadcast.nickname} won ${formatWinAmount(
|
||||
broadcast.totalWin,
|
||||
)}`,
|
||||
}))
|
||||
: [{ id: 'empty', message: '' }]
|
||||
const marqueeTitles =
|
||||
jackpotBroadcasts.length > 0
|
||||
? [
|
||||
...titles.map((title) => ({ ...title, id: `${title.id}:first` })),
|
||||
...titles.map((title) => ({ ...title, id: `${title.id}:second` })),
|
||||
]
|
||||
: titles
|
||||
const hasBroadcasts = jackpotBroadcasts.length > 0
|
||||
const titles = useMemo(
|
||||
() =>
|
||||
hasBroadcasts
|
||||
? jackpotBroadcasts.map((broadcast) => ({
|
||||
id: broadcast.id,
|
||||
message: `Player ${broadcast.nickname} won ${formatWinAmount(
|
||||
broadcast.totalWin,
|
||||
)}`,
|
||||
}))
|
||||
: [{ id: 'empty', message: '' }],
|
||||
[hasBroadcasts, jackpotBroadcasts],
|
||||
)
|
||||
const carouselTitles = useMemo(
|
||||
() =>
|
||||
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 (
|
||||
<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}
|
||||
/>
|
||||
<div className="relative h-design-28 min-w-0 flex-1 overflow-hidden">
|
||||
<div
|
||||
className={
|
||||
jackpotBroadcasts.length > 0
|
||||
? 'desktop-title-horizontal-marquee'
|
||||
: ''
|
||||
}
|
||||
>
|
||||
{marqueeTitles.map((title) => (
|
||||
<div
|
||||
className="flex h-design-28 shrink-0 items-center whitespace-nowrap !text-[#FF970F]"
|
||||
key={title.id}
|
||||
>
|
||||
{title.message}
|
||||
</div>
|
||||
))}
|
||||
<div className="h-full overflow-hidden" ref={emblaRef}>
|
||||
<div className="flex h-full items-center gap-design-80">
|
||||
{carouselTitles.map((title) => (
|
||||
<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}
|
||||
>
|
||||
{title.message}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -2,10 +2,10 @@ import i18n from 'i18next'
|
||||
import { initReactI18next } from 'react-i18next'
|
||||
|
||||
import { DEFAULT_APP_LANGUAGE, SUPPORTED_LANGUAGES } from '@/constants/system'
|
||||
import enUSCommon from '@/locales/en-US/common'
|
||||
import idIDCommon from '@/locales/id-ID/common'
|
||||
import msMYCommon from '@/locales/ms-MY/common'
|
||||
import zhCNCommon from '@/locales/zh-CN/common'
|
||||
import enUS from '@/locales/en-US'
|
||||
import idID from '@/locales/id-ID'
|
||||
import msMY from '@/locales/ms-MY'
|
||||
import zhCN from '@/locales/zh-CN'
|
||||
import { getStoredAppLanguage, setStoredAppLanguage } from '@/store/auth'
|
||||
|
||||
export type AppLanguage = (typeof SUPPORTED_LANGUAGES)[number]
|
||||
@@ -53,16 +53,16 @@ void i18n.use(initReactI18next).init({
|
||||
},
|
||||
resources: {
|
||||
'zh-CN': {
|
||||
common: zhCNCommon,
|
||||
common: zhCN,
|
||||
},
|
||||
'en-US': {
|
||||
common: enUSCommon,
|
||||
common: enUS,
|
||||
},
|
||||
'ms-MY': {
|
||||
common: msMYCommon,
|
||||
common: msMY,
|
||||
},
|
||||
'id-ID': {
|
||||
common: idIDCommon,
|
||||
common: idID,
|
||||
},
|
||||
},
|
||||
defaultNS: 'common',
|
||||
|
||||
@@ -525,24 +525,6 @@
|
||||
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 {
|
||||
syntax: "<angle>";
|
||||
inherits: false;
|
||||
|
||||
Reference in New Issue
Block a user