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",
|
"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
40
pnpm-lock.yaml
generated
@@ -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: {}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user