refactor(constants): 提取常量并优化国际化配置
- 创建API相关常量文件,包括响应码、HTTP状态码、请求头等 - 将认证相关常量从auth模块提取到独立的常量文件 - 在API客户端中使用新定义的常量替换硬编码值 - 更新认证API和服务中对常量的引用 - 在国际化配置中创建统一的文案常量以减少重复 - 将认证表单验证规则改为使用常量配置
This commit is contained in:
24
src/constants/api.ts
Normal file
24
src/constants/api.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/** @description 业务响应成功状态码,响应 envelope.code 等于该值代表成功。 */
|
||||
export const API_SUCCESS_CODE = 1
|
||||
|
||||
/** @description 接口层统一引用的常用 HTTP 状态码集合。 */
|
||||
export const HTTP_STATUS = {
|
||||
noContent: 204,
|
||||
requestTimeout: 408,
|
||||
unauthorized: 401,
|
||||
} as const
|
||||
|
||||
/** @description 接口请求头字段名集合。 */
|
||||
export const REQUEST_HEADERS = {
|
||||
accept: 'Accept',
|
||||
authToken: 'auth-token',
|
||||
authorization: 'Authorization',
|
||||
lang: 'lang',
|
||||
userToken: 'user-token',
|
||||
} as const
|
||||
|
||||
/** @description Authorization 请求头的 Bearer 前缀。 */
|
||||
export const AUTHORIZATION_BEARER_PREFIX = 'Bearer '
|
||||
|
||||
/** @description JSON 内容类型标识,用于响应体内容类型判定。 */
|
||||
export const CONTENT_TYPE_JSON = 'application/json'
|
||||
@@ -34,3 +34,27 @@ export const AUTH_TOKEN_CACHE_SKEW_MS = 30_000
|
||||
|
||||
/** @description 认证错误翻译 key 的统一前缀。 */
|
||||
export const AUTH_ERROR_KEY_PREFIX = 'auth.'
|
||||
|
||||
/** @description 密码最小长度,用于登录与注册表单校验。 */
|
||||
export const PASSWORD_MIN_LENGTH = 6
|
||||
|
||||
/** @description 密码最大长度,用于登录与注册表单校验。 */
|
||||
export const PASSWORD_MAX_LENGTH = 32
|
||||
|
||||
/** @description 邀请码最大长度,用于注册表单校验。 */
|
||||
export const INVITE_CODE_MAX_LENGTH = 32
|
||||
|
||||
/** @description 短信验证码重发倒计时兜底秒数,后端未返回有效 expiresIn 时使用。 */
|
||||
export const SMS_CODE_COOLDOWN_FALLBACK_SECONDS = 60
|
||||
|
||||
/** @description 发送短信验证码的业务事件类型,当前固定为注册场景。 */
|
||||
export const SMS_SEND_EVENT_REGISTER = 'user_register'
|
||||
|
||||
/** @description 注册邀请码默认值,URL 未携带邀请码参数时兜底。 */
|
||||
export const DEFAULT_REGISTER_INVITE_CODE = 'D97DBC16'
|
||||
|
||||
/** @description 注册邀请码在 URL 查询参数中的字段名。 */
|
||||
export const REGISTER_INVITE_CODE_QUERY_PARAM = 'registerInviteCode'
|
||||
|
||||
/** @description 触发登录提示(弹窗/Toast)的最小去重间隔,单位为毫秒。 */
|
||||
export const LOGIN_PROMPT_DEDUP_MS = 1200
|
||||
|
||||
@@ -240,6 +240,36 @@ export const ENTRY_NOTICE_CONFIRM_INTERVAL_MS = 24 * 60 * 60 * 1000
|
||||
/** @description 游戏投注记录每页加载条数。 */
|
||||
export const GAME_HISTORY_PAGE_SIZE = 20
|
||||
|
||||
/** @description 列表类接口默认分页条数,用于财务、钱包、公告、投注单等列表。 */
|
||||
export const DEFAULT_LIST_PAGE_SIZE = 20
|
||||
|
||||
/** @description 财务配置类查询(充值提现配置、充值档位)缓存新鲜时间,单位为毫秒。 */
|
||||
export const FINANCE_CONFIG_QUERY_STALE_TIME_MS = 5 * 60 * 1000
|
||||
|
||||
/** @description 提现表单默认币种代码。 */
|
||||
export const DEFAULT_CURRENCY_CODE = 'MYR'
|
||||
|
||||
/** @description 自动托管「单次中奖超过」停止规则的默认阈值。 */
|
||||
export const AUTO_HOSTING_DEFAULT_SINGLE_WIN_THRESHOLD = 50_000
|
||||
|
||||
/** @description 全站大奖播报最大保留条数。 */
|
||||
export const MAX_JACKPOT_BROADCAST_COUNT = 20
|
||||
|
||||
/** @description 实时连接延迟信号「优」阈值,单位为毫秒。 */
|
||||
export const CONNECTION_LATENCY_GOOD_MS = 80
|
||||
|
||||
/** @description 实时连接延迟信号「良」阈值,同时用于判定连接是否健康,单位为毫秒。 */
|
||||
export const CONNECTION_LATENCY_FAIR_MS = 150
|
||||
|
||||
/** @description 实时连接延迟信号「中」阈值,单位为毫秒。 */
|
||||
export const CONNECTION_LATENCY_POOR_MS = 300
|
||||
|
||||
/** @description 提现收款邮箱校验正则。 */
|
||||
export const WITHDRAW_EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
|
||||
/** @description 提现收款手机号校验正则,6-20 位数字,允许前导 +。 */
|
||||
export const WITHDRAW_PHONE_PATTERN = /^\+?\d{6,20}$/
|
||||
|
||||
/** @description 提现页快捷法币金额选项。 */
|
||||
export const QUICK_FIAT_AMOUNTS = [3, 30, 50, 100, 200, 500] as const
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './api'
|
||||
export * from './auth'
|
||||
export * from './game'
|
||||
export * from './system'
|
||||
|
||||
@@ -48,6 +48,9 @@ export const QUERY_RETRYABLE_STATUS_CODES = [
|
||||
/** @description 桌面端布局切换起始断点,单位为像素。 */
|
||||
export const DESKTOP_LAYOUT_MIN_WIDTH_PX = 1024
|
||||
|
||||
/** @description 移动端布局判定断点(最大宽度),单位为像素。 */
|
||||
export const MOBILE_LAYOUT_BREAKPOINT_PX = 768
|
||||
|
||||
/** @description 应用支持的语言代码列表。 */
|
||||
export const SUPPORTED_LANGUAGES = ['zh-CN', 'en-US', 'ms-MY', 'id-ID'] as const
|
||||
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { AUTH_ENDPOINTS, AUTH_SKIP_REFRESH_CONTEXT_KEY } from '@/constants'
|
||||
import {
|
||||
API_SUCCESS_CODE,
|
||||
AUTH_ENDPOINTS,
|
||||
AUTH_SKIP_REFRESH_CONTEXT_KEY,
|
||||
SMS_SEND_EVENT_REGISTER,
|
||||
} from '@/constants'
|
||||
import { api } from '@/lib/api/api-client'
|
||||
import { ApiError } from '@/lib/api/api-error'
|
||||
import type { AuthSessionInput } from '@/store/auth'
|
||||
@@ -34,7 +39,7 @@ function unwrapEnvelope<T>(
|
||||
response: ApiResponse<T>,
|
||||
fallbackErrorKey = 'auth.errors.requestFailed',
|
||||
) {
|
||||
if (response.code === 1) {
|
||||
if (response.code === API_SUCCESS_CODE) {
|
||||
return response.data
|
||||
}
|
||||
|
||||
@@ -172,7 +177,7 @@ export async function sendSmsCode(
|
||||
AUTH_ENDPOINTS.sendSmsCode,
|
||||
{
|
||||
json: {
|
||||
event: 'user_register',
|
||||
event: SMS_SEND_EVENT_REGISTER,
|
||||
mobile: payload.mobile,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { SMS_SEND_EVENT_REGISTER } from '@/constants'
|
||||
import type { AuthSessionInput, AuthUser } from '@/store/auth'
|
||||
|
||||
export interface AuthApiEnvelope<T> {
|
||||
@@ -70,7 +71,7 @@ export interface RegisterRequestDto {
|
||||
}
|
||||
|
||||
export interface SendSmsCodeRequestDto {
|
||||
event: 'user_register'
|
||||
event: typeof SMS_SEND_EVENT_REGISTER
|
||||
mobile: string
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import {
|
||||
DEFAULT_REGISTER_INVITE_CODE,
|
||||
REGISTER_INVITE_CODE_QUERY_PARAM,
|
||||
} from '@/constants'
|
||||
import i18n from '@/i18n'
|
||||
import { notify } from '@/lib/notify'
|
||||
import { useAuthStore } from '@/store/auth'
|
||||
@@ -15,9 +19,6 @@ interface UseRegisterFormOptions {
|
||||
onSuccess?: () => void
|
||||
}
|
||||
|
||||
const REGISTER_INVITE_CODE_QUERY_PARAM = 'registerInviteCode'
|
||||
const DEFAULT_REGISTER_INVITE_CODE = 'D97DBC16'
|
||||
|
||||
function getInitialRegisterInviteCode() {
|
||||
if (typeof window === 'undefined') {
|
||||
return DEFAULT_REGISTER_INVITE_CODE
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { SMS_CODE_COOLDOWN_FALLBACK_SECONDS } from '@/constants'
|
||||
import i18n from '@/i18n'
|
||||
import { notify } from '@/lib/notify'
|
||||
import { sendSmsCode } from '../api/auth-api'
|
||||
import { toAuthSubmitErrorKey } from './auth-error-key'
|
||||
|
||||
const FALLBACK_SMS_CODE_COOLDOWN_SECONDS = 60
|
||||
|
||||
export function useSendSmsCode() {
|
||||
const [remainingSeconds, setRemainingSeconds] = useState(0)
|
||||
const mutation = useMutation({
|
||||
@@ -24,7 +23,7 @@ export function useSendSmsCode() {
|
||||
setRemainingSeconds(
|
||||
result.expiresIn > 0
|
||||
? result.expiresIn
|
||||
: FALLBACK_SMS_CODE_COOLDOWN_SECONDS,
|
||||
: SMS_CODE_COOLDOWN_FALLBACK_SECONDS,
|
||||
)
|
||||
notify.success(i18n.t('auth.register.sms.success'))
|
||||
},
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
import {
|
||||
INVITE_CODE_MAX_LENGTH,
|
||||
PASSWORD_MAX_LENGTH,
|
||||
PASSWORD_MIN_LENGTH,
|
||||
} from '@/constants'
|
||||
|
||||
const usernameSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
@@ -12,8 +18,8 @@ const captchaSchema = z
|
||||
|
||||
const passwordSchema = z
|
||||
.string()
|
||||
.min(6, 'auth.validation.password.min')
|
||||
.max(32, 'auth.validation.password.max')
|
||||
.min(PASSWORD_MIN_LENGTH, 'auth.validation.password.min')
|
||||
.max(PASSWORD_MAX_LENGTH, 'auth.validation.password.max')
|
||||
|
||||
export const loginFormSchema = z.object({
|
||||
password: passwordSchema,
|
||||
@@ -28,7 +34,7 @@ export const registerFormSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, 'auth.validation.inviteCode.required')
|
||||
.max(32, 'auth.validation.inviteCode.max'),
|
||||
.max(INVITE_CODE_MAX_LENGTH, 'auth.validation.inviteCode.max'),
|
||||
password: passwordSchema,
|
||||
mobile: usernameSchema,
|
||||
})
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { FINANCE_API_ENDPOINTS } from '@/constants'
|
||||
import {
|
||||
API_SUCCESS_CODE,
|
||||
DEFAULT_LIST_PAGE_SIZE,
|
||||
FINANCE_API_ENDPOINTS,
|
||||
} from '@/constants'
|
||||
import { api } from '@/lib/api/api-client'
|
||||
import { ApiError } from '@/lib/api/api-error'
|
||||
import type { ApiResponse } from '@/type'
|
||||
@@ -30,7 +34,7 @@ function unwrapFinanceEnvelope<T>(
|
||||
response: ApiResponse<T>,
|
||||
fallbackMessage = 'Finance request failed',
|
||||
) {
|
||||
if (response.code === 1) {
|
||||
if (response.code === API_SUCCESS_CODE) {
|
||||
return response.data
|
||||
}
|
||||
|
||||
@@ -194,7 +198,7 @@ function normalizeFinanceOrderList(dto: FinanceOrderListDto): FinanceOrderList {
|
||||
list: (dto.list ?? []).map(normalizeFinanceOrderItem),
|
||||
pagination: {
|
||||
page: dto.pagination?.page ?? 1,
|
||||
page_size: dto.pagination?.page_size ?? 20,
|
||||
page_size: dto.pagination?.page_size ?? DEFAULT_LIST_PAGE_SIZE,
|
||||
total: dto.pagination?.total ?? 0,
|
||||
},
|
||||
}
|
||||
@@ -237,7 +241,8 @@ function normalizeWalletRecordList(dto: WalletRecordListDto): WalletRecordList {
|
||||
list: (dto.list ?? []).map(normalizeWalletRecordItem),
|
||||
pagination: {
|
||||
page: dto.pagination?.page ?? dto.page ?? 1,
|
||||
page_size: dto.pagination?.page_size ?? dto.page_size ?? 20,
|
||||
page_size:
|
||||
dto.pagination?.page_size ?? dto.page_size ?? DEFAULT_LIST_PAGE_SIZE,
|
||||
total: dto.pagination?.total ?? dto.total ?? 0,
|
||||
},
|
||||
}
|
||||
@@ -301,7 +306,7 @@ export async function getDepositOrderList(params?: {
|
||||
{
|
||||
searchParams: {
|
||||
page: String(params?.page ?? 1),
|
||||
page_size: String(params?.pageSize ?? 20),
|
||||
page_size: String(params?.pageSize ?? DEFAULT_LIST_PAGE_SIZE),
|
||||
},
|
||||
},
|
||||
)
|
||||
@@ -322,7 +327,7 @@ export async function getWithdrawOrderList(params?: {
|
||||
{
|
||||
searchParams: {
|
||||
page: String(params?.page ?? 1),
|
||||
page_size: String(params?.pageSize ?? 20),
|
||||
page_size: String(params?.pageSize ?? DEFAULT_LIST_PAGE_SIZE),
|
||||
},
|
||||
},
|
||||
)
|
||||
@@ -344,7 +349,7 @@ export async function getWalletRecordList(params?: {
|
||||
{
|
||||
searchParams: {
|
||||
page: String(params?.page ?? 1),
|
||||
page_size: String(params?.pageSize ?? 20),
|
||||
page_size: String(params?.pageSize ?? DEFAULT_LIST_PAGE_SIZE),
|
||||
type: params?.type ?? 'payout',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { GAME_API_ENDPOINTS } from '@/constants'
|
||||
import {
|
||||
API_SUCCESS_CODE,
|
||||
DEFAULT_LIST_PAGE_SIZE,
|
||||
GAME_API_ENDPOINTS,
|
||||
} from '@/constants'
|
||||
import { api } from '@/lib/api/api-client'
|
||||
import { ApiError } from '@/lib/api/api-error'
|
||||
import type { ApiResponse } from '@/type'
|
||||
@@ -51,7 +55,7 @@ function unwrapGameEnvelope<T>(
|
||||
response: ApiResponse<T>,
|
||||
fallbackMessage = 'Game request failed',
|
||||
) {
|
||||
if (response.code === 1) {
|
||||
if (response.code === API_SUCCESS_CODE) {
|
||||
return response.data
|
||||
}
|
||||
|
||||
@@ -424,7 +428,7 @@ export async function getNoticeList(params?: {
|
||||
const response = await api.get<NoticeListDto>(GAME_API_ENDPOINTS.noticeList, {
|
||||
searchParams: {
|
||||
page: String(params?.page ?? 1),
|
||||
page_size: String(params?.pageSize ?? 20),
|
||||
page_size: String(params?.pageSize ?? DEFAULT_LIST_PAGE_SIZE),
|
||||
},
|
||||
})
|
||||
const dto = unwrapGameEnvelope(
|
||||
@@ -478,7 +482,7 @@ export async function getGameBetMyOrders(params: {
|
||||
{
|
||||
json: {
|
||||
page: params.page ?? 1,
|
||||
page_size: params.pageSize ?? 20,
|
||||
page_size: params.pageSize ?? DEFAULT_LIST_PAGE_SIZE,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { GAME_API_ENDPOINTS } from '@/constants'
|
||||
import { API_SUCCESS_CODE, GAME_API_ENDPOINTS } from '@/constants'
|
||||
import { api } from '@/lib/api/api-client'
|
||||
import { ApiError } from '@/lib/api/api-error'
|
||||
import type { ApiResponse } from '@/type'
|
||||
@@ -16,7 +16,7 @@ interface GamePeriodHistoryDto {
|
||||
function unwrapPeriodHistoryEnvelope(
|
||||
response: ApiResponse<GamePeriodHistoryDto>,
|
||||
) {
|
||||
if (response.code === 1) {
|
||||
if (response.code === API_SUCCESS_CODE) {
|
||||
return response.data
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { startTransition, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { MOBILE_LAYOUT_BREAKPOINT_PX } from '@/constants'
|
||||
import { getGameLobbyInit } from '@/features/game'
|
||||
import { EntryNoticeGateModal } from '@/features/game/components'
|
||||
import { MobileEntry } from '@/features/game/entry/mobile-entry.tsx'
|
||||
@@ -74,7 +75,8 @@ export function EntryPage() {
|
||||
return false
|
||||
}
|
||||
|
||||
return window.matchMedia('(max-width: 768px)').matches
|
||||
return window.matchMedia(`(max-width: ${MOBILE_LAYOUT_BREAKPOINT_PX}px)`)
|
||||
.matches
|
||||
})
|
||||
|
||||
useDocumentMetadata({
|
||||
@@ -189,7 +191,9 @@ export function EntryPage() {
|
||||
return
|
||||
}
|
||||
|
||||
const mediaQuery = window.matchMedia('(max-width: 768px)')
|
||||
const mediaQuery = window.matchMedia(
|
||||
`(max-width: ${MOBILE_LAYOUT_BREAKPOINT_PX}px)`,
|
||||
)
|
||||
const syncLayout = (event?: MediaQueryListEvent) => {
|
||||
setIsMobile(event?.matches ?? mediaQuery.matches)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { DEFAULT_APP_LANGUAGE } from '@/constants'
|
||||
import {
|
||||
DEFAULT_APP_LANGUAGE,
|
||||
FINANCE_CONFIG_QUERY_STALE_TIME_MS,
|
||||
} from '@/constants'
|
||||
import { getDepositTierList } from '@/features/game/api'
|
||||
|
||||
export function useDepositTierList() {
|
||||
@@ -12,6 +15,6 @@ export function useDepositTierList() {
|
||||
return useQuery({
|
||||
queryKey: ['finance', 'deposit-tier-list', language],
|
||||
queryFn: () => getDepositTierList(),
|
||||
staleTime: 5 * 60 * 1000,
|
||||
staleTime: FINANCE_CONFIG_QUERY_STALE_TIME_MS,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { DEFAULT_APP_LANGUAGE } from '@/constants'
|
||||
import {
|
||||
DEFAULT_APP_LANGUAGE,
|
||||
FINANCE_CONFIG_QUERY_STALE_TIME_MS,
|
||||
} from '@/constants'
|
||||
import { getDepositWithdrawConfig } from '@/features/game/api'
|
||||
|
||||
export function useDepositWithdrawConfig() {
|
||||
@@ -12,6 +15,6 @@ export function useDepositWithdrawConfig() {
|
||||
return useQuery({
|
||||
queryKey: ['finance', 'deposit-withdraw-config', language],
|
||||
queryFn: () => getDepositWithdrawConfig(),
|
||||
staleTime: 5 * 60 * 1000,
|
||||
staleTime: FINANCE_CONFIG_QUERY_STALE_TIME_MS,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,12 +2,11 @@ import { useInfiniteQuery } from '@tanstack/react-query'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { DEFAULT_LIST_PAGE_SIZE } from '@/constants'
|
||||
import { getDepositOrderList, getWithdrawOrderList } from '@/features/game/api'
|
||||
|
||||
export type FinanceRecordType = 'deposit' | 'withdraw'
|
||||
|
||||
const FINANCE_RECORD_PAGE_SIZE = 20
|
||||
|
||||
const FINANCE_RECORD_TYPE_OPTIONS: Array<{
|
||||
key: FinanceRecordType
|
||||
labelKey: string
|
||||
@@ -46,11 +45,11 @@ export function useFinanceRecordsVm({ enabled }: { enabled: boolean }) {
|
||||
recordType === 'deposit'
|
||||
? getDepositOrderList({
|
||||
page: pageParam,
|
||||
pageSize: FINANCE_RECORD_PAGE_SIZE,
|
||||
pageSize: DEFAULT_LIST_PAGE_SIZE,
|
||||
})
|
||||
: getWithdrawOrderList({
|
||||
page: pageParam,
|
||||
pageSize: FINANCE_RECORD_PAGE_SIZE,
|
||||
pageSize: DEFAULT_LIST_PAGE_SIZE,
|
||||
}),
|
||||
enabled,
|
||||
getNextPageParam: (lastPage) => {
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import {
|
||||
CONNECTION_LATENCY_FAIR_MS,
|
||||
CONNECTION_LATENCY_GOOD_MS,
|
||||
CONNECTION_LATENCY_POOR_MS,
|
||||
} from '@/constants'
|
||||
import { useAppLanguage } from '@/features/game/hooks/use-app-language'
|
||||
import {
|
||||
isDesktopFullscreen,
|
||||
@@ -74,7 +79,7 @@ function resolveSignalPresentation(input: {
|
||||
} satisfies SignalPresentation
|
||||
}
|
||||
|
||||
if (input.latencyMs <= 80) {
|
||||
if (input.latencyMs <= CONNECTION_LATENCY_GOOD_MS) {
|
||||
return {
|
||||
activeBars: 4,
|
||||
latencyLabel: String(input.latencyMs),
|
||||
@@ -82,7 +87,7 @@ function resolveSignalPresentation(input: {
|
||||
} satisfies SignalPresentation
|
||||
}
|
||||
|
||||
if (input.latencyMs <= 150) {
|
||||
if (input.latencyMs <= CONNECTION_LATENCY_FAIR_MS) {
|
||||
return {
|
||||
activeBars: 3,
|
||||
latencyLabel: String(input.latencyMs),
|
||||
@@ -90,7 +95,7 @@ function resolveSignalPresentation(input: {
|
||||
} satisfies SignalPresentation
|
||||
}
|
||||
|
||||
if (input.latencyMs <= 300) {
|
||||
if (input.latencyMs <= CONNECTION_LATENCY_POOR_MS) {
|
||||
return {
|
||||
activeBars: 2,
|
||||
latencyLabel: String(input.latencyMs),
|
||||
|
||||
@@ -3,9 +3,9 @@ import dayjs from 'dayjs'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { DEFAULT_LIST_PAGE_SIZE } from '@/constants'
|
||||
import { getWalletRecordList } from '@/features/game/api'
|
||||
|
||||
const WALLET_RECORD_PAGE_SIZE = 20
|
||||
const WALLET_RECORD_TYPE = 'payout'
|
||||
|
||||
function formatWalletAmount(value: string, locale: string) {
|
||||
@@ -47,7 +47,7 @@ export function useWalletRecordsVm({ enabled }: { enabled: boolean }) {
|
||||
queryFn: ({ pageParam }) =>
|
||||
getWalletRecordList({
|
||||
page: pageParam,
|
||||
pageSize: WALLET_RECORD_PAGE_SIZE,
|
||||
pageSize: DEFAULT_LIST_PAGE_SIZE,
|
||||
type: WALLET_RECORD_TYPE,
|
||||
}),
|
||||
enabled,
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { DEFAULT_WITHDRAW_CONFIG, QUICK_FIAT_AMOUNTS } from '@/constants'
|
||||
import {
|
||||
DEFAULT_CURRENCY_CODE,
|
||||
DEFAULT_WITHDRAW_CONFIG,
|
||||
QUICK_FIAT_AMOUNTS,
|
||||
WITHDRAW_EMAIL_PATTERN,
|
||||
WITHDRAW_PHONE_PATTERN,
|
||||
} from '@/constants'
|
||||
import type { DepositWithdrawConfig } from '@/features/game/api'
|
||||
import { useDepositWithdrawConfig } from '@/features/game/hooks/use-deposit-withdraw-config'
|
||||
import { useAuthStore } from '@/store'
|
||||
@@ -47,7 +53,7 @@ function isValidEmail(value: string) {
|
||||
return false
|
||||
}
|
||||
|
||||
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.trim())
|
||||
return WITHDRAW_EMAIL_PATTERN.test(value.trim())
|
||||
}
|
||||
|
||||
function isValidPhone(value: string) {
|
||||
@@ -57,7 +63,7 @@ function isValidPhone(value: string) {
|
||||
return false
|
||||
}
|
||||
|
||||
return /^\+?\d{6,20}$/.test(normalized)
|
||||
return WITHDRAW_PHONE_PATTERN.test(normalized)
|
||||
}
|
||||
|
||||
export function useWithdrawVm() {
|
||||
@@ -88,7 +94,7 @@ export function useWithdrawVm() {
|
||||
const [amount, setAmountState] = useState(0)
|
||||
const [hasInitializedAmount, setHasInitializedAmount] = useState(false)
|
||||
const [currencyCode, setCurrencyCode] = useState(
|
||||
config.currencies[0]?.code ?? 'MYR',
|
||||
config.currencies[0]?.code ?? DEFAULT_CURRENCY_CODE,
|
||||
)
|
||||
const [paymentChannelCode, setPaymentChannelCode] = useState('')
|
||||
const [bankCode, setBankCode] = useState('')
|
||||
@@ -207,7 +213,7 @@ export function useWithdrawVm() {
|
||||
}, [locale, maxWithdrawAmount, selectedCurrency.code, selectedRate])
|
||||
|
||||
const resetForm = useCallback(() => {
|
||||
const nextCurrencyCode = config.currencies[0]?.code ?? 'MYR'
|
||||
const nextCurrencyCode = config.currencies[0]?.code ?? DEFAULT_CURRENCY_CODE
|
||||
const nextCurrency = getActiveCurrencyCode(
|
||||
config.currencies,
|
||||
nextCurrencyCode,
|
||||
|
||||
@@ -5,6 +5,7 @@ import { CenterModal } from '@/components/center-modal.tsx'
|
||||
import { SmartBackground } from '@/components/smart-background.tsx'
|
||||
import { Input } from '@/components/ui/input.tsx'
|
||||
import { Switch } from '@/components/ui/switch.tsx'
|
||||
import { AUTO_HOSTING_DEFAULT_SINGLE_WIN_THRESHOLD } from '@/constants'
|
||||
import { notify } from '@/lib/notify'
|
||||
import { useModalStore } from '@/store'
|
||||
import { useAuthStore } from '@/store/auth'
|
||||
@@ -51,7 +52,9 @@ function DesktopAutoSettingModal() {
|
||||
const [balanceLimitEnabled, setBalanceLimitEnabled] = useState(false)
|
||||
const [balanceLimitValue, setBalanceLimitValue] = useState('0')
|
||||
const [singleWinLimitEnabled, setSingleWinLimitEnabled] = useState(false)
|
||||
const [singleWinLimitValue, setSingleWinLimitValue] = useState('50000')
|
||||
const [singleWinLimitValue, setSingleWinLimitValue] = useState(
|
||||
String(AUTO_HOSTING_DEFAULT_SINGLE_WIN_THRESHOLD),
|
||||
)
|
||||
const [jackpotStopEnabled, setJackpotStopEnabled] = useState(false)
|
||||
|
||||
function handleClose() {
|
||||
|
||||
@@ -15,6 +15,7 @@ import userInfoBg from '@/assets/system/userInfo-bg.webp'
|
||||
import { CenterModal } from '@/components/center-modal.tsx'
|
||||
import { SmartBackground } from '@/components/smart-background.tsx'
|
||||
import { SmartImage } from '@/components/smart-image.tsx'
|
||||
import { REGISTER_INVITE_CODE_QUERY_PARAM } from '@/constants'
|
||||
import { logoutWithPassword } from '@/features/auth/api/auth-api'
|
||||
import DesktopFinanceRecordsTab from '@/features/game/modal/desktop/desktop-finance-records-tab'
|
||||
import DesktopWalletRecordsTab from '@/features/game/modal/desktop/desktop-wallet-records-tab'
|
||||
@@ -47,8 +48,6 @@ const USER_INFO_TABS: Array<{
|
||||
},
|
||||
]
|
||||
|
||||
const REGISTER_INVITE_CODE_QUERY_PARAM = 'registerInviteCode'
|
||||
|
||||
function createRegisterInviteUrl(inviteCode: string) {
|
||||
const url = new URL(window.location.href)
|
||||
|
||||
|
||||
@@ -2,14 +2,19 @@ import ky, { HTTPError, type Options, TimeoutError } from 'ky'
|
||||
import {
|
||||
ACCESS_TOKEN_REFRESH_SKEW_MS,
|
||||
API_ERROR_MESSAGES,
|
||||
API_SUCCESS_CODE,
|
||||
AUTH_REFRESH_ATTEMPTED_CONTEXT_KEY,
|
||||
AUTH_REFRESH_ENDPOINT,
|
||||
AUTH_RELOGIN_REQUIRED_CODES,
|
||||
AUTH_SKIP_REFRESH_CONTEXT_KEY,
|
||||
AUTH_TOKEN_CACHE_SKEW_MS,
|
||||
AUTH_TOKEN_ENDPOINT,
|
||||
AUTHORIZATION_BEARER_PREFIX,
|
||||
CONTENT_TYPE_JSON,
|
||||
DEFAULT_REQUEST_ACCEPT_HEADER,
|
||||
DEFAULT_REQUEST_TIMEOUT_MS,
|
||||
HTTP_STATUS,
|
||||
REQUEST_HEADERS,
|
||||
} from '@/constants'
|
||||
import type { AuthTokenDto } from '@/features/auth/api/types'
|
||||
import { getPreferredLanguage, isSupportedLanguage } from '@/i18n'
|
||||
@@ -65,13 +70,13 @@ function normalizeApiBaseUrl(baseUrl: string | undefined) {
|
||||
}
|
||||
|
||||
async function parseResponseBody(response: Response) {
|
||||
if (response.status === 204) {
|
||||
if (response.status === HTTP_STATUS.noContent) {
|
||||
return null
|
||||
}
|
||||
|
||||
const contentType = response.headers.get('content-type') ?? ''
|
||||
|
||||
if (contentType.includes('application/json')) {
|
||||
if (contentType.includes(CONTENT_TYPE_JSON)) {
|
||||
return response.json()
|
||||
}
|
||||
|
||||
@@ -110,7 +115,7 @@ async function toApiError(error: unknown) {
|
||||
if (error instanceof TimeoutError) {
|
||||
return new ApiError({
|
||||
message: API_ERROR_MESSAGES.timeout,
|
||||
status: 408,
|
||||
status: HTTP_STATUS.requestTimeout,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -143,14 +148,20 @@ const apiClient = ky.create({
|
||||
hooks: {
|
||||
beforeRequest: [
|
||||
({ request }) => {
|
||||
request.headers.set('Accept', DEFAULT_REQUEST_ACCEPT_HEADER)
|
||||
request.headers.set('lang', getRequestLanguage())
|
||||
request.headers.set(
|
||||
REQUEST_HEADERS.accept,
|
||||
DEFAULT_REQUEST_ACCEPT_HEADER,
|
||||
)
|
||||
request.headers.set(REQUEST_HEADERS.lang, getRequestLanguage())
|
||||
|
||||
const token = useAuthStore.getState().accessToken
|
||||
|
||||
if (token) {
|
||||
request.headers.set('Authorization', `Bearer ${token}`)
|
||||
request.headers.set('user-token', token)
|
||||
request.headers.set(
|
||||
REQUEST_HEADERS.authorization,
|
||||
`${AUTHORIZATION_BEARER_PREFIX}${token}`,
|
||||
)
|
||||
request.headers.set(REQUEST_HEADERS.userToken, token)
|
||||
}
|
||||
|
||||
if (shouldLogRequests) {
|
||||
@@ -222,14 +233,14 @@ function assertValidAuthEnvelope(data: unknown) {
|
||||
throw new ApiError({
|
||||
data,
|
||||
message: getApiEnvelopeMessage(data),
|
||||
status: 401,
|
||||
status: HTTP_STATUS.unauthorized,
|
||||
})
|
||||
}
|
||||
|
||||
function unwrapEnvelopeData<T>(response: ApiResponse<T>) {
|
||||
assertValidAuthEnvelope(response)
|
||||
|
||||
if (response.code === 1) {
|
||||
if (response.code === API_SUCCESS_CODE) {
|
||||
return response.data
|
||||
}
|
||||
|
||||
@@ -333,8 +344,8 @@ function createHeaders(headersInit?: Options['headers']) {
|
||||
async function buildRequestOptions(input: string, options?: Options) {
|
||||
const headers = createHeaders(options?.headers)
|
||||
|
||||
if (shouldAttachAuthToken(input) && !headers.has('auth-token')) {
|
||||
headers.set('auth-token', await fetchAuthToken())
|
||||
if (shouldAttachAuthToken(input) && !headers.has(REQUEST_HEADERS.authToken)) {
|
||||
headers.set(REQUEST_HEADERS.authToken, await fetchAuthToken())
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -361,7 +372,7 @@ async function request<TResponse>(input: string, options?: Options) {
|
||||
} catch (error) {
|
||||
if (
|
||||
error instanceof HTTPError &&
|
||||
error.response.status === 401 &&
|
||||
error.response.status === HTTP_STATUS.unauthorized &&
|
||||
input !== AUTH_REFRESH_ENDPOINT &&
|
||||
options?.context?.[AUTH_SKIP_REFRESH_CONTEXT_KEY] !== true &&
|
||||
options?.context?.[AUTH_REFRESH_ATTEMPTED_CONTEXT_KEY] !== true
|
||||
@@ -379,7 +390,10 @@ async function request<TResponse>(input: string, options?: Options) {
|
||||
}
|
||||
}
|
||||
|
||||
if (error instanceof HTTPError && error.response.status === 401) {
|
||||
if (
|
||||
error instanceof HTTPError &&
|
||||
error.response.status === HTTP_STATUS.unauthorized
|
||||
) {
|
||||
handleUnauthorizedSession()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { LOGIN_PROMPT_DEDUP_MS } from '@/constants'
|
||||
import i18n from '@/i18n'
|
||||
import { notify } from '@/lib/notify'
|
||||
import { queryClient } from '@/lib/query/query-client'
|
||||
@@ -16,8 +17,6 @@ let authInitializationPromise: Promise<void> | null = null
|
||||
let refreshSessionPromise: Promise<boolean> | null = null
|
||||
let lastLoginPromptAt = 0
|
||||
|
||||
const LOGIN_PROMPT_DEDUP_MS = 1200
|
||||
|
||||
interface ClearAuthenticatedSessionOptions {
|
||||
clearBrowserStorage?: boolean
|
||||
clearQueryCache?: boolean
|
||||
|
||||
@@ -1,3 +1,76 @@
|
||||
/* 以下为多语言中重复出现的文案,统一声明一次后在下方各 key 复用,避免同一文案多处声明。 */
|
||||
/** @description 登录的统一文案。 */
|
||||
const TEXT_LOGIN = 'Login'
|
||||
|
||||
/** @description 注册的统一文案。 */
|
||||
const TEXT_REGISTER = 'Register'
|
||||
|
||||
/** @description “是”的统一文案。 */
|
||||
const TEXT_YES = 'Yes'
|
||||
|
||||
/** @description “否”的统一文案。 */
|
||||
const TEXT_NO = 'No'
|
||||
|
||||
/** @description “查看”的统一文案。 */
|
||||
const TEXT_VIEW = 'View'
|
||||
|
||||
/** @description 自动托管的统一文案。 */
|
||||
const TEXT_AUTO_HOSTING = 'Auto Spin'
|
||||
|
||||
/** @description 钱包流水的统一文案。 */
|
||||
const TEXT_WALLET_RECORDS = 'Wallet Records'
|
||||
|
||||
/** @description 站内消息的统一文案。 */
|
||||
const TEXT_SITE_MESSAGES = 'Messages'
|
||||
|
||||
/** @description 分页“第x页/共x条”的统一文案。 */
|
||||
const TEXT_PAGE_INDICATOR = 'Page {{page}} / {{total}} total'
|
||||
|
||||
/** @description 上一页的统一文案。 */
|
||||
const TEXT_PREV_PAGE = 'Previous'
|
||||
|
||||
/** @description 下一页的统一文案。 */
|
||||
const TEXT_NEXT_PAGE = 'Next'
|
||||
|
||||
/** @description “时间”的统一文案。 */
|
||||
const TEXT_TIME = 'Time'
|
||||
|
||||
/** @description 超过单次投注限额的统一文案。 */
|
||||
const TEXT_BET_LIMIT_EXCEEDED = 'Single bet limit exceeded'
|
||||
|
||||
/** @description “提交中...”的统一文案。 */
|
||||
const TEXT_SUBMITTING = 'Submitting...'
|
||||
|
||||
/** @description 手机号字段标签的统一文案。 */
|
||||
const TEXT_MOBILE_LABEL = 'Mobile:'
|
||||
|
||||
/** @description 手机号输入占位的统一文案。 */
|
||||
const TEXT_MOBILE_PLACEHOLDER = 'Enter mobile number'
|
||||
|
||||
/** @description 密码字段标签的统一文案。 */
|
||||
const TEXT_PASSWORD_LABEL = 'Password:'
|
||||
|
||||
/** @description 密码输入占位的统一文案。 */
|
||||
const TEXT_PASSWORD_PLACEHOLDER = 'Enter password'
|
||||
|
||||
/** @description “确认”按钮的统一文案。 */
|
||||
const TEXT_CONFIRM = 'Confirm'
|
||||
|
||||
/** @description “加载中...”的统一文案。 */
|
||||
const TEXT_LOADING = 'Loading...'
|
||||
|
||||
/** @description 货币类型的统一文案。 */
|
||||
const TEXT_CURRENCY_TYPE = 'Currency Type'
|
||||
|
||||
/** @description 支付渠道的统一文案。 */
|
||||
const TEXT_PAYMENT_CHANNEL = 'Payment Channel'
|
||||
|
||||
/** @description “已封盘”的统一文案。 */
|
||||
const TEXT_LOCKED = 'Locked'
|
||||
|
||||
/** @description “已结算”的统一文案。 */
|
||||
const TEXT_SETTLED = 'Settled'
|
||||
|
||||
export default {
|
||||
nav: {
|
||||
home: 'Home',
|
||||
@@ -88,8 +161,8 @@ export default {
|
||||
},
|
||||
phases: {
|
||||
betting: 'Betting',
|
||||
locked: 'Locked',
|
||||
settled: 'Settled',
|
||||
locked: TEXT_LOCKED,
|
||||
settled: TEXT_SETTLED,
|
||||
},
|
||||
roundBettingStart: {
|
||||
title: 'Round {{roundId}}',
|
||||
@@ -99,8 +172,8 @@ export default {
|
||||
unifiedBetHint: 'Unified bet',
|
||||
totalBet: 'Total bet',
|
||||
canBet: 'Can bet',
|
||||
yes: 'Yes',
|
||||
no: 'No',
|
||||
yes: TEXT_YES,
|
||||
no: TEXT_NO,
|
||||
quickBet: 'Quick bet 08',
|
||||
clearPending: 'Clear pending',
|
||||
autoModeDemo: 'Auto mode demo',
|
||||
@@ -108,16 +181,16 @@ export default {
|
||||
},
|
||||
modals: {
|
||||
login: {
|
||||
title: 'Login',
|
||||
title: TEXT_LOGIN,
|
||||
},
|
||||
register: {
|
||||
title: 'Register',
|
||||
title: TEXT_REGISTER,
|
||||
},
|
||||
notice: {
|
||||
title: 'Event Notice',
|
||||
content:
|
||||
'This area will later load the real event announcement body, rich media, and a longer scrollable message. The current version focuses on shared multilingual modal wiring.',
|
||||
check: 'View',
|
||||
check: TEXT_VIEW,
|
||||
},
|
||||
entryNotice: {
|
||||
title: 'Site Notice',
|
||||
@@ -142,7 +215,7 @@ export default {
|
||||
topup: 'Top Up',
|
||||
},
|
||||
autoSetting: {
|
||||
title: 'Auto Spin',
|
||||
title: TEXT_AUTO_HOSTING,
|
||||
startAutoSpin: 'Start Auto Spin',
|
||||
rows: {
|
||||
stopIfBalanceLowerThan: 'Stop if balance is lower than',
|
||||
@@ -155,8 +228,8 @@ export default {
|
||||
tabs: {
|
||||
profile: 'Profile',
|
||||
financeRecords: 'Top Up / Withdraw Records',
|
||||
walletRecords: 'Wallet Records',
|
||||
message: 'Messages',
|
||||
walletRecords: TEXT_WALLET_RECORDS,
|
||||
message: TEXT_SITE_MESSAGES,
|
||||
},
|
||||
profile: {
|
||||
name: 'Name',
|
||||
@@ -169,7 +242,7 @@ export default {
|
||||
'My signature is as unique as my personality. This area will later display the real profile summary.',
|
||||
},
|
||||
message: {
|
||||
title: 'Messages',
|
||||
title: TEXT_SITE_MESSAGES,
|
||||
back: 'Back',
|
||||
loading: 'Loading messages...',
|
||||
loadFailed: 'Failed to load messages. Please try again later.',
|
||||
@@ -178,7 +251,7 @@ export default {
|
||||
unread: 'Unread',
|
||||
eventBonus:
|
||||
'[Top-up Bonus Event] From October 1 to October 7, 2026, claim your rebate rewards...',
|
||||
check: 'View',
|
||||
check: TEXT_VIEW,
|
||||
deleteRecords: 'Delete records',
|
||||
},
|
||||
financeRecords: {
|
||||
@@ -190,9 +263,9 @@ export default {
|
||||
loading: 'Loading records...',
|
||||
loadFailed: 'Failed to load records. Please try again later.',
|
||||
empty: 'No records yet',
|
||||
page: 'Page {{page}} / {{total}} total',
|
||||
previous: 'Previous',
|
||||
next: 'Next',
|
||||
page: TEXT_PAGE_INDICATOR,
|
||||
previous: TEXT_PREV_PAGE,
|
||||
next: TEXT_NEXT_PAGE,
|
||||
},
|
||||
walletRecords: {
|
||||
amount: 'Amount',
|
||||
@@ -201,12 +274,12 @@ export default {
|
||||
empty: 'No wallet records yet',
|
||||
loadFailed: 'Failed to load wallet records. Please try again later.',
|
||||
loading: 'Loading wallet records...',
|
||||
next: 'Next',
|
||||
page: 'Page {{page}} / {{total}} total',
|
||||
previous: 'Previous',
|
||||
next: TEXT_NEXT_PAGE,
|
||||
page: TEXT_PAGE_INDICATOR,
|
||||
previous: TEXT_PREV_PAGE,
|
||||
remark: 'Remark',
|
||||
time: 'Time',
|
||||
type: 'Wallet Records',
|
||||
time: TEXT_TIME,
|
||||
type: TEXT_WALLET_RECORDS,
|
||||
},
|
||||
},
|
||||
withdrawTopup: {
|
||||
@@ -238,8 +311,8 @@ export default {
|
||||
dialog: {
|
||||
close: 'Close alert',
|
||||
confirm: 'OK',
|
||||
no: 'No',
|
||||
yes: 'Yes',
|
||||
no: TEXT_NO,
|
||||
yes: TEXT_YES,
|
||||
},
|
||||
modal: {
|
||||
close: 'Close modal',
|
||||
@@ -256,7 +329,7 @@ export default {
|
||||
inviteLinkCopyFailed:
|
||||
'Failed to copy invite link. Please copy it manually.',
|
||||
insufficientBalance: 'Insufficient balance. Please adjust your bet.',
|
||||
betLimitExceeded: 'Single bet limit exceeded',
|
||||
betLimitExceeded: TEXT_BET_LIMIT_EXCEEDED,
|
||||
betUnavailable: 'Betting is not available for this round',
|
||||
betPlaced: 'Bet placed successfully',
|
||||
noRecentSuccessfulBet:
|
||||
@@ -282,7 +355,7 @@ export default {
|
||||
common: {
|
||||
arrowIconAlt: 'Arrow',
|
||||
actions: {
|
||||
submitting: 'Submitting...',
|
||||
submitting: TEXT_SUBMITTING,
|
||||
},
|
||||
passwordVisibility: {
|
||||
hide: 'Hide password',
|
||||
@@ -295,12 +368,12 @@ export default {
|
||||
},
|
||||
fields: {
|
||||
username: {
|
||||
label: 'Mobile:',
|
||||
placeholder: 'Enter mobile number',
|
||||
label: TEXT_MOBILE_LABEL,
|
||||
placeholder: TEXT_MOBILE_PLACEHOLDER,
|
||||
},
|
||||
password: {
|
||||
label: 'Password:',
|
||||
placeholder: 'Enter password',
|
||||
label: TEXT_PASSWORD_LABEL,
|
||||
placeholder: TEXT_PASSWORD_PLACEHOLDER,
|
||||
},
|
||||
},
|
||||
footer: {
|
||||
@@ -314,20 +387,20 @@ export default {
|
||||
},
|
||||
register: {
|
||||
actions: {
|
||||
submit: 'Register',
|
||||
submit: TEXT_REGISTER,
|
||||
},
|
||||
fields: {
|
||||
mobile: {
|
||||
label: 'Mobile:',
|
||||
placeholder: 'Enter mobile number',
|
||||
label: TEXT_MOBILE_LABEL,
|
||||
placeholder: TEXT_MOBILE_PLACEHOLDER,
|
||||
},
|
||||
captcha: {
|
||||
label: 'Code:',
|
||||
placeholder: 'Enter verification code',
|
||||
},
|
||||
password: {
|
||||
label: 'Password:',
|
||||
placeholder: 'Enter password',
|
||||
label: TEXT_PASSWORD_LABEL,
|
||||
placeholder: TEXT_PASSWORD_PLACEHOLDER,
|
||||
},
|
||||
confirmPassword: {
|
||||
label: 'Confirm Password:',
|
||||
@@ -393,23 +466,23 @@ export default {
|
||||
bgm: 'BGM',
|
||||
id: 'ID',
|
||||
fullscreen: 'Full Screen',
|
||||
login: 'Login',
|
||||
register: 'Register',
|
||||
login: TEXT_LOGIN,
|
||||
register: TEXT_REGISTER,
|
||||
},
|
||||
control: {
|
||||
trend: 'Trend',
|
||||
map: 'Map',
|
||||
selected: 'Selected',
|
||||
totalBet: 'Total Bet',
|
||||
confirm: 'Confirm',
|
||||
confirm: TEXT_CONFIRM,
|
||||
selectNumbers: 'Select Numbers',
|
||||
insufficientBalance: 'Insufficient Balance',
|
||||
betLimitExceeded: 'Limit Exceeded',
|
||||
submitting: 'Submitting...',
|
||||
submitting: TEXT_SUBMITTING,
|
||||
actions: {
|
||||
clear: 'Clear',
|
||||
repeat: 'Repeat',
|
||||
'auto-spin': 'Auto Spin',
|
||||
'auto-spin': TEXT_AUTO_HOSTING,
|
||||
},
|
||||
},
|
||||
status: {
|
||||
@@ -423,7 +496,7 @@ export default {
|
||||
description: '(Accepting Bets)',
|
||||
},
|
||||
locked: {
|
||||
label: 'Locked',
|
||||
label: TEXT_LOCKED,
|
||||
description: '(Betting Closed)',
|
||||
},
|
||||
revealing: {
|
||||
@@ -445,7 +518,7 @@ export default {
|
||||
},
|
||||
animal: {
|
||||
insufficientBalanceRecharge: 'Insufficient balance, please top up',
|
||||
betLimitExceeded: 'Single bet limit exceeded',
|
||||
betLimitExceeded: TEXT_BET_LIMIT_EXCEEDED,
|
||||
loading: 'Loading',
|
||||
selectionLimitReached: 'Selection limit exceeded',
|
||||
tapToEnter: 'Tap To Enter',
|
||||
@@ -460,29 +533,29 @@ export default {
|
||||
orderNo: 'Order No.',
|
||||
roundId: 'Round ID',
|
||||
numbers: 'Bet Numbers',
|
||||
createdAt: 'Time',
|
||||
createdAt: TEXT_TIME,
|
||||
settledAt: 'Settled At',
|
||||
totalPoolAmount: 'Bet Amount',
|
||||
winningResult: 'Winning Result',
|
||||
payout: 'Win Amount',
|
||||
empty: 'No history yet',
|
||||
end: 'No more records',
|
||||
loading: 'Loading...',
|
||||
settled: 'Settled',
|
||||
loading: TEXT_LOADING,
|
||||
settled: TEXT_SETTLED,
|
||||
},
|
||||
periodHistory: {
|
||||
title: 'Draw Result History',
|
||||
close: 'Close draw result history',
|
||||
empty: 'No draw results yet',
|
||||
failed: 'Failed to load draw results',
|
||||
loading: 'Loading...',
|
||||
loading: TEXT_LOADING,
|
||||
retry: 'Retry',
|
||||
},
|
||||
topup: {
|
||||
title: 'Top-up Config',
|
||||
platformCoinLabel: 'Platform Coin',
|
||||
currencyLabel: 'Currency Type',
|
||||
channelLabel: 'Payment Channel',
|
||||
currencyLabel: TEXT_CURRENCY_TYPE,
|
||||
channelLabel: TEXT_PAYMENT_CHANNEL,
|
||||
rateHint:
|
||||
'Exchange rates are for reference only. The final amount follows the top-up-time rate.',
|
||||
tier: {
|
||||
@@ -524,13 +597,13 @@ export default {
|
||||
feeNotice:
|
||||
'Transactions between RM10 and RM99.99 will be charged a minimum withdrawal fee of RM 1.',
|
||||
cancel: 'Cancel',
|
||||
confirm: 'Confirm',
|
||||
confirm: TEXT_CONFIRM,
|
||||
submitSuccess: 'Withdrawal request submitted',
|
||||
withdrawal: 'Withdrawal',
|
||||
fields: {
|
||||
diamondAmount: 'Withdrawal Diamond Amount',
|
||||
currencyType: 'Currency Type',
|
||||
paymentChannel: 'Payment Channel',
|
||||
currencyType: TEXT_CURRENCY_TYPE,
|
||||
paymentChannel: TEXT_PAYMENT_CHANNEL,
|
||||
bankCode: 'Bank Code',
|
||||
cardHolderName: 'Card Holder Name',
|
||||
bankAccountNumber: 'Bank Account Number',
|
||||
|
||||
@@ -1,3 +1,76 @@
|
||||
/* 以下为多语言中重复出现的文案,统一声明一次后在下方各 key 复用,避免同一文案多处声明。 */
|
||||
/** @description 登录的统一文案。 */
|
||||
const TEXT_LOGIN = 'Masuk'
|
||||
|
||||
/** @description 注册的统一文案。 */
|
||||
const TEXT_REGISTER = 'Daftar'
|
||||
|
||||
/** @description “是”的统一文案。 */
|
||||
const TEXT_YES = 'Ya'
|
||||
|
||||
/** @description “否”的统一文案。 */
|
||||
const TEXT_NO = 'Tidak'
|
||||
|
||||
/** @description “查看”的统一文案。 */
|
||||
const TEXT_VIEW = 'Lihat'
|
||||
|
||||
/** @description 自动托管的统一文案。 */
|
||||
const TEXT_AUTO_HOSTING = 'Auto Spin'
|
||||
|
||||
/** @description 钱包流水的统一文案。 */
|
||||
const TEXT_WALLET_RECORDS = 'Riwayat Dompet'
|
||||
|
||||
/** @description 站内消息的统一文案。 */
|
||||
const TEXT_SITE_MESSAGES = 'Pesan'
|
||||
|
||||
/** @description 分页“第x页/共x条”的统一文案。 */
|
||||
const TEXT_PAGE_INDICATOR = 'Halaman {{page}} / total {{total}}'
|
||||
|
||||
/** @description 上一页的统一文案。 */
|
||||
const TEXT_PREV_PAGE = 'Sebelumnya'
|
||||
|
||||
/** @description 下一页的统一文案。 */
|
||||
const TEXT_NEXT_PAGE = 'Berikutnya'
|
||||
|
||||
/** @description “时间”的统一文案。 */
|
||||
const TEXT_TIME = 'Waktu'
|
||||
|
||||
/** @description 超过单次投注限额的统一文案。 */
|
||||
const TEXT_BET_LIMIT_EXCEEDED = 'Melebihi batas taruhan tunggal'
|
||||
|
||||
/** @description “提交中...”的统一文案。 */
|
||||
const TEXT_SUBMITTING = 'Mengirim...'
|
||||
|
||||
/** @description 手机号字段标签的统一文案。 */
|
||||
const TEXT_MOBILE_LABEL = 'Nomor Ponsel:'
|
||||
|
||||
/** @description 手机号输入占位的统一文案。 */
|
||||
const TEXT_MOBILE_PLACEHOLDER = 'Masukkan nomor ponsel'
|
||||
|
||||
/** @description 密码字段标签的统一文案。 */
|
||||
const TEXT_PASSWORD_LABEL = 'Kata Sandi:'
|
||||
|
||||
/** @description 密码输入占位的统一文案。 */
|
||||
const TEXT_PASSWORD_PLACEHOLDER = 'Masukkan kata sandi'
|
||||
|
||||
/** @description “确认”按钮的统一文案。 */
|
||||
const TEXT_CONFIRM = 'Konfirmasi'
|
||||
|
||||
/** @description “加载中...”的统一文案。 */
|
||||
const TEXT_LOADING = 'Memuat...'
|
||||
|
||||
/** @description 货币类型的统一文案。 */
|
||||
const TEXT_CURRENCY_TYPE = 'Jenis Mata Uang'
|
||||
|
||||
/** @description 支付渠道的统一文案。 */
|
||||
const TEXT_PAYMENT_CHANNEL = 'Saluran Pembayaran'
|
||||
|
||||
/** @description “已封盘”的统一文案。 */
|
||||
const TEXT_LOCKED = 'Terkunci'
|
||||
|
||||
/** @description “已结算”的统一文案。 */
|
||||
const TEXT_SETTLED = 'Selesai'
|
||||
|
||||
export default {
|
||||
nav: {
|
||||
home: 'Beranda',
|
||||
@@ -87,8 +160,8 @@ export default {
|
||||
},
|
||||
phases: {
|
||||
betting: 'Betting',
|
||||
locked: 'Terkunci',
|
||||
settled: 'Selesai',
|
||||
locked: TEXT_LOCKED,
|
||||
settled: TEXT_SETTLED,
|
||||
},
|
||||
roundBettingStart: {
|
||||
title: 'Ronde {{roundId}}',
|
||||
@@ -98,8 +171,8 @@ export default {
|
||||
unifiedBetHint: 'Bet seragam',
|
||||
totalBet: 'Total bet',
|
||||
canBet: 'Bisa bet',
|
||||
yes: 'Ya',
|
||||
no: 'Tidak',
|
||||
yes: TEXT_YES,
|
||||
no: TEXT_NO,
|
||||
quickBet: 'Quick bet 08',
|
||||
clearPending: 'Hapus pending',
|
||||
autoModeDemo: 'Demo mode auto',
|
||||
@@ -107,16 +180,16 @@ export default {
|
||||
},
|
||||
modals: {
|
||||
login: {
|
||||
title: 'Masuk',
|
||||
title: TEXT_LOGIN,
|
||||
},
|
||||
register: {
|
||||
title: 'Daftar',
|
||||
title: TEXT_REGISTER,
|
||||
},
|
||||
notice: {
|
||||
title: 'Pengumuman Acara',
|
||||
content:
|
||||
'Bagian ini nantinya akan memuat konten pengumuman acara yang sebenarnya, materi visual, dan pesan panjang yang dapat digulir. Versi saat ini fokus pada sambungan modal multibahasa.',
|
||||
check: 'Lihat',
|
||||
check: TEXT_VIEW,
|
||||
},
|
||||
entryNotice: {
|
||||
title: 'Pengumuman Situs',
|
||||
@@ -141,7 +214,7 @@ export default {
|
||||
topup: 'Isi Ulang',
|
||||
},
|
||||
autoSetting: {
|
||||
title: 'Auto Spin',
|
||||
title: TEXT_AUTO_HOSTING,
|
||||
startAutoSpin: 'Mulai Auto Spin',
|
||||
rows: {
|
||||
stopIfBalanceLowerThan: 'Berhenti jika saldo lebih rendah dari',
|
||||
@@ -154,8 +227,8 @@ export default {
|
||||
tabs: {
|
||||
profile: 'Profil',
|
||||
financeRecords: 'Riwayat Isi Ulang / Penarikan',
|
||||
walletRecords: 'Riwayat Dompet',
|
||||
message: 'Pesan',
|
||||
walletRecords: TEXT_WALLET_RECORDS,
|
||||
message: TEXT_SITE_MESSAGES,
|
||||
},
|
||||
profile: {
|
||||
name: 'Nama',
|
||||
@@ -168,7 +241,7 @@ export default {
|
||||
'Tanda tanganku seunik diriku. Bagian ini nantinya akan menampilkan ringkasan profil yang sebenarnya.',
|
||||
},
|
||||
message: {
|
||||
title: 'Pesan',
|
||||
title: TEXT_SITE_MESSAGES,
|
||||
back: 'Kembali',
|
||||
loading: 'Memuat pesan...',
|
||||
loadFailed: 'Gagal memuat pesan. Silakan coba lagi nanti.',
|
||||
@@ -177,7 +250,7 @@ export default {
|
||||
unread: 'Belum dibaca',
|
||||
eventBonus:
|
||||
'[Event Bonus Isi Ulang] Dari 1 Oktober hingga 7 Oktober 2026, klaim hadiah rebate kamu...',
|
||||
check: 'Lihat',
|
||||
check: TEXT_VIEW,
|
||||
deleteRecords: 'Hapus riwayat',
|
||||
},
|
||||
financeRecords: {
|
||||
@@ -189,9 +262,9 @@ export default {
|
||||
loading: 'Memuat riwayat...',
|
||||
loadFailed: 'Gagal memuat riwayat. Silakan coba lagi nanti.',
|
||||
empty: 'Belum ada riwayat',
|
||||
page: 'Halaman {{page}} / total {{total}}',
|
||||
previous: 'Sebelumnya',
|
||||
next: 'Berikutnya',
|
||||
page: TEXT_PAGE_INDICATOR,
|
||||
previous: TEXT_PREV_PAGE,
|
||||
next: TEXT_NEXT_PAGE,
|
||||
},
|
||||
walletRecords: {
|
||||
amount: 'Jumlah',
|
||||
@@ -200,12 +273,12 @@ export default {
|
||||
empty: 'Belum ada riwayat dompet',
|
||||
loadFailed: 'Gagal memuat riwayat dompet. Silakan coba lagi nanti.',
|
||||
loading: 'Memuat riwayat dompet...',
|
||||
next: 'Berikutnya',
|
||||
page: 'Halaman {{page}} / total {{total}}',
|
||||
previous: 'Sebelumnya',
|
||||
next: TEXT_NEXT_PAGE,
|
||||
page: TEXT_PAGE_INDICATOR,
|
||||
previous: TEXT_PREV_PAGE,
|
||||
remark: 'Catatan',
|
||||
time: 'Waktu',
|
||||
type: 'Riwayat Dompet',
|
||||
time: TEXT_TIME,
|
||||
type: TEXT_WALLET_RECORDS,
|
||||
},
|
||||
},
|
||||
withdrawTopup: {
|
||||
@@ -237,8 +310,8 @@ export default {
|
||||
dialog: {
|
||||
close: 'Tutup notifikasi',
|
||||
confirm: 'OK',
|
||||
no: 'Tidak',
|
||||
yes: 'Ya',
|
||||
no: TEXT_NO,
|
||||
yes: TEXT_YES,
|
||||
},
|
||||
modal: {
|
||||
close: 'Tutup modal',
|
||||
@@ -255,7 +328,7 @@ export default {
|
||||
inviteLinkCopyFailed:
|
||||
'Gagal menyalin tautan undangan. Silakan salin secara manual.',
|
||||
insufficientBalance: 'Saldo tidak cukup. Silakan sesuaikan taruhan.',
|
||||
betLimitExceeded: 'Melebihi batas taruhan tunggal',
|
||||
betLimitExceeded: TEXT_BET_LIMIT_EXCEEDED,
|
||||
betUnavailable: 'Taruhan tidak tersedia untuk ronde ini',
|
||||
betPlaced: 'Taruhan berhasil dikirim',
|
||||
noRecentSuccessfulBet:
|
||||
@@ -282,7 +355,7 @@ export default {
|
||||
common: {
|
||||
arrowIconAlt: 'Panah',
|
||||
actions: {
|
||||
submitting: 'Mengirim...',
|
||||
submitting: TEXT_SUBMITTING,
|
||||
},
|
||||
passwordVisibility: {
|
||||
hide: 'Sembunyikan kata sandi',
|
||||
@@ -291,16 +364,16 @@ export default {
|
||||
},
|
||||
login: {
|
||||
actions: {
|
||||
submit: 'Masuk',
|
||||
submit: TEXT_LOGIN,
|
||||
},
|
||||
fields: {
|
||||
username: {
|
||||
label: 'Nomor Ponsel:',
|
||||
placeholder: 'Masukkan nomor ponsel',
|
||||
label: TEXT_MOBILE_LABEL,
|
||||
placeholder: TEXT_MOBILE_PLACEHOLDER,
|
||||
},
|
||||
password: {
|
||||
label: 'Kata Sandi:',
|
||||
placeholder: 'Masukkan kata sandi',
|
||||
label: TEXT_PASSWORD_LABEL,
|
||||
placeholder: TEXT_PASSWORD_PLACEHOLDER,
|
||||
},
|
||||
},
|
||||
footer: {
|
||||
@@ -314,20 +387,20 @@ export default {
|
||||
},
|
||||
register: {
|
||||
actions: {
|
||||
submit: 'Daftar',
|
||||
submit: TEXT_REGISTER,
|
||||
},
|
||||
fields: {
|
||||
mobile: {
|
||||
label: 'Nomor Ponsel:',
|
||||
placeholder: 'Masukkan nomor ponsel',
|
||||
label: TEXT_MOBILE_LABEL,
|
||||
placeholder: TEXT_MOBILE_PLACEHOLDER,
|
||||
},
|
||||
captcha: {
|
||||
label: 'Kode:',
|
||||
placeholder: 'Masukkan kode verifikasi',
|
||||
},
|
||||
password: {
|
||||
label: 'Kata Sandi:',
|
||||
placeholder: 'Masukkan kata sandi',
|
||||
label: TEXT_PASSWORD_LABEL,
|
||||
placeholder: TEXT_PASSWORD_PLACEHOLDER,
|
||||
},
|
||||
confirmPassword: {
|
||||
label: 'Konfirmasi Kata Sandi:',
|
||||
@@ -352,7 +425,7 @@ export default {
|
||||
submitFailed: 'Gagal mengirim kode. Silakan coba lagi nanti.',
|
||||
},
|
||||
send: 'Ambil kode',
|
||||
sending: 'Mengirim...',
|
||||
sending: TEXT_SUBMITTING,
|
||||
success: 'Kode verifikasi telah dikirim.',
|
||||
},
|
||||
},
|
||||
@@ -389,27 +462,27 @@ export default {
|
||||
header: {
|
||||
systemTime: 'Waktu Sistem',
|
||||
rules: 'Aturan',
|
||||
message: 'Pesan',
|
||||
message: TEXT_SITE_MESSAGES,
|
||||
bgm: 'BGM',
|
||||
id: 'ID',
|
||||
fullscreen: 'Layar',
|
||||
login: 'Masuk',
|
||||
register: 'Daftar',
|
||||
login: TEXT_LOGIN,
|
||||
register: TEXT_REGISTER,
|
||||
},
|
||||
control: {
|
||||
trend: 'Tren',
|
||||
map: 'Peta',
|
||||
selected: 'Dipilih',
|
||||
totalBet: 'Total Bet',
|
||||
confirm: 'Konfirmasi',
|
||||
confirm: TEXT_CONFIRM,
|
||||
selectNumbers: 'Pilih Nombor',
|
||||
insufficientBalance: 'Saldo Tidak Cukup',
|
||||
betLimitExceeded: 'Batas Terlampaui',
|
||||
submitting: 'Mengirim...',
|
||||
submitting: TEXT_SUBMITTING,
|
||||
actions: {
|
||||
clear: 'Hapus',
|
||||
repeat: 'Ulang',
|
||||
'auto-spin': 'Auto Spin',
|
||||
'auto-spin': TEXT_AUTO_HOSTING,
|
||||
},
|
||||
},
|
||||
status: {
|
||||
@@ -423,7 +496,7 @@ export default {
|
||||
description: '(Menerima Bet)',
|
||||
},
|
||||
locked: {
|
||||
label: 'Terkunci',
|
||||
label: TEXT_LOCKED,
|
||||
description: '(Bet Ditutup)',
|
||||
},
|
||||
revealing: {
|
||||
@@ -445,7 +518,7 @@ export default {
|
||||
},
|
||||
animal: {
|
||||
insufficientBalanceRecharge: 'Saldo tidak cukup, silakan isi ulang',
|
||||
betLimitExceeded: 'Melebihi batas taruhan tunggal',
|
||||
betLimitExceeded: TEXT_BET_LIMIT_EXCEEDED,
|
||||
loading: 'Memuat',
|
||||
selectionLimitReached: 'Melebihi pilihan yang diizinkan',
|
||||
tapToEnter: 'Ketuk Untuk Masuk',
|
||||
@@ -460,29 +533,29 @@ export default {
|
||||
orderNo: 'No. Order',
|
||||
roundId: 'ID Ronde',
|
||||
numbers: 'Nomor Taruhan',
|
||||
createdAt: 'Waktu',
|
||||
createdAt: TEXT_TIME,
|
||||
settledAt: 'Waktu Selesai',
|
||||
totalPoolAmount: 'Jumlah Taruhan',
|
||||
winningResult: 'Hasil Menang',
|
||||
payout: 'Jumlah Menang',
|
||||
empty: 'Belum ada riwayat',
|
||||
end: 'Tidak ada catatan lagi',
|
||||
loading: 'Memuat...',
|
||||
settled: 'Selesai',
|
||||
loading: TEXT_LOADING,
|
||||
settled: TEXT_SETTLED,
|
||||
},
|
||||
periodHistory: {
|
||||
title: 'Riwayat Hasil Undian',
|
||||
close: 'Tutup riwayat hasil undian',
|
||||
empty: 'Belum ada hasil undian',
|
||||
failed: 'Gagal memuat hasil undian',
|
||||
loading: 'Memuat...',
|
||||
loading: TEXT_LOADING,
|
||||
retry: 'Coba lagi',
|
||||
},
|
||||
topup: {
|
||||
title: 'Konfigurasi Isi Ulang',
|
||||
platformCoinLabel: 'Koin Platform',
|
||||
currencyLabel: 'Jenis Mata Uang',
|
||||
channelLabel: 'Saluran Pembayaran',
|
||||
currencyLabel: TEXT_CURRENCY_TYPE,
|
||||
channelLabel: TEXT_PAYMENT_CHANNEL,
|
||||
rateHint:
|
||||
'Kurs hanya sebagai referensi. Jumlah akhir mengikuti kurs saat isi ulang.',
|
||||
tier: {
|
||||
@@ -525,13 +598,13 @@ export default {
|
||||
feeNotice:
|
||||
'Transaksi antara RM10 dan RM99.99 akan dikenakan biaya penarikan minimum RM 1.',
|
||||
cancel: 'Batal',
|
||||
confirm: 'Konfirmasi',
|
||||
confirm: TEXT_CONFIRM,
|
||||
submitSuccess: 'Permintaan penarikan berhasil dikirim',
|
||||
withdrawal: 'Penarikan',
|
||||
fields: {
|
||||
diamondAmount: 'Jumlah Berlian Penarikan',
|
||||
currencyType: 'Jenis Mata Uang',
|
||||
paymentChannel: 'Saluran Pembayaran',
|
||||
currencyType: TEXT_CURRENCY_TYPE,
|
||||
paymentChannel: TEXT_PAYMENT_CHANNEL,
|
||||
bankCode: 'Kode Bank',
|
||||
cardHolderName: 'Nama Pemilik Rekening',
|
||||
bankAccountNumber: 'Nomor Rekening Bank',
|
||||
|
||||
@@ -1,3 +1,76 @@
|
||||
/* 以下为多语言中重复出现的文案,统一声明一次后在下方各 key 复用,避免同一文案多处声明。 */
|
||||
/** @description 登录的统一文案。 */
|
||||
const TEXT_LOGIN = 'Log Masuk'
|
||||
|
||||
/** @description 注册的统一文案。 */
|
||||
const TEXT_REGISTER = 'Daftar'
|
||||
|
||||
/** @description “是”的统一文案。 */
|
||||
const TEXT_YES = 'Ya'
|
||||
|
||||
/** @description “否”的统一文案。 */
|
||||
const TEXT_NO = 'Tidak'
|
||||
|
||||
/** @description “查看”的统一文案。 */
|
||||
const TEXT_VIEW = 'Semak'
|
||||
|
||||
/** @description 自动托管的统一文案。 */
|
||||
const TEXT_AUTO_HOSTING = 'Putaran Auto'
|
||||
|
||||
/** @description 钱包流水的统一文案。 */
|
||||
const TEXT_WALLET_RECORDS = 'Rekod Dompet'
|
||||
|
||||
/** @description 站内消息的统一文案。 */
|
||||
const TEXT_SITE_MESSAGES = 'Mesej'
|
||||
|
||||
/** @description 分页“第x页/共x条”的统一文案。 */
|
||||
const TEXT_PAGE_INDICATOR = 'Halaman {{page}} / jumlah {{total}}'
|
||||
|
||||
/** @description 上一页的统一文案。 */
|
||||
const TEXT_PREV_PAGE = 'Sebelumnya'
|
||||
|
||||
/** @description 下一页的统一文案。 */
|
||||
const TEXT_NEXT_PAGE = 'Seterusnya'
|
||||
|
||||
/** @description “时间”的统一文案。 */
|
||||
const TEXT_TIME = 'Masa'
|
||||
|
||||
/** @description 超过单次投注限额的统一文案。 */
|
||||
const TEXT_BET_LIMIT_EXCEEDED = 'Melebihi had taruhan tunggal'
|
||||
|
||||
/** @description “提交中...”的统一文案。 */
|
||||
const TEXT_SUBMITTING = 'Menghantar...'
|
||||
|
||||
/** @description 手机号字段标签的统一文案。 */
|
||||
const TEXT_MOBILE_LABEL = 'Nombor Telefon:'
|
||||
|
||||
/** @description 手机号输入占位的统一文案。 */
|
||||
const TEXT_MOBILE_PLACEHOLDER = 'Masukkan nombor telefon'
|
||||
|
||||
/** @description 密码字段标签的统一文案。 */
|
||||
const TEXT_PASSWORD_LABEL = 'Kata Laluan:'
|
||||
|
||||
/** @description 密码输入占位的统一文案。 */
|
||||
const TEXT_PASSWORD_PLACEHOLDER = 'Masukkan kata laluan'
|
||||
|
||||
/** @description “确认”按钮的统一文案。 */
|
||||
const TEXT_CONFIRM = 'Sahkan'
|
||||
|
||||
/** @description “加载中...”的统一文案。 */
|
||||
const TEXT_LOADING = 'Memuatkan...'
|
||||
|
||||
/** @description 货币类型的统一文案。 */
|
||||
const TEXT_CURRENCY_TYPE = 'Jenis Mata Wang'
|
||||
|
||||
/** @description 支付渠道的统一文案。 */
|
||||
const TEXT_PAYMENT_CHANNEL = 'Saluran Pembayaran'
|
||||
|
||||
/** @description “已封盘”的统一文案。 */
|
||||
const TEXT_LOCKED = 'Dikunci'
|
||||
|
||||
/** @description “已结算”的统一文案。 */
|
||||
const TEXT_SETTLED = 'Selesai'
|
||||
|
||||
export default {
|
||||
nav: {
|
||||
home: 'Laman Utama',
|
||||
@@ -90,8 +163,8 @@ export default {
|
||||
},
|
||||
phases: {
|
||||
betting: 'Taruhan',
|
||||
locked: 'Dikunci',
|
||||
settled: 'Selesai',
|
||||
locked: TEXT_LOCKED,
|
||||
settled: TEXT_SETTLED,
|
||||
},
|
||||
roundBettingStart: {
|
||||
title: 'Pusingan {{roundId}}',
|
||||
@@ -101,8 +174,8 @@ export default {
|
||||
unifiedBetHint: 'Taruhan seragam',
|
||||
totalBet: 'Jumlah taruhan',
|
||||
canBet: 'Boleh taruhan',
|
||||
yes: 'Ya',
|
||||
no: 'Tidak',
|
||||
yes: TEXT_YES,
|
||||
no: TEXT_NO,
|
||||
quickBet: 'Taruhan cepat 08',
|
||||
clearPending: 'Kosongkan belum sah',
|
||||
autoModeDemo: 'Demo mod auto',
|
||||
@@ -110,16 +183,16 @@ export default {
|
||||
},
|
||||
modals: {
|
||||
login: {
|
||||
title: 'Log Masuk',
|
||||
title: TEXT_LOGIN,
|
||||
},
|
||||
register: {
|
||||
title: 'Daftar',
|
||||
title: TEXT_REGISTER,
|
||||
},
|
||||
notice: {
|
||||
title: 'Notis Acara',
|
||||
content:
|
||||
'Bahagian ini akan memuatkan kandungan notis acara sebenar, bahan visual, dan mesej boleh skrol yang lebih panjang. Versi semasa memfokuskan sambungan modal pelbagai bahasa.',
|
||||
check: 'Semak',
|
||||
check: TEXT_VIEW,
|
||||
},
|
||||
entryNotice: {
|
||||
title: 'Notis Laman',
|
||||
@@ -144,7 +217,7 @@ export default {
|
||||
topup: 'Tambah Nilai',
|
||||
},
|
||||
autoSetting: {
|
||||
title: 'Putaran Auto',
|
||||
title: TEXT_AUTO_HOSTING,
|
||||
startAutoSpin: 'Mula Putaran Auto',
|
||||
rows: {
|
||||
stopIfBalanceLowerThan: 'Henti jika baki lebih rendah daripada',
|
||||
@@ -157,8 +230,8 @@ export default {
|
||||
tabs: {
|
||||
profile: 'Profil',
|
||||
financeRecords: 'Rekod Tambah Nilai / Pengeluaran',
|
||||
walletRecords: 'Rekod Dompet',
|
||||
message: 'Mesej',
|
||||
walletRecords: TEXT_WALLET_RECORDS,
|
||||
message: TEXT_SITE_MESSAGES,
|
||||
},
|
||||
profile: {
|
||||
name: 'Nama',
|
||||
@@ -171,7 +244,7 @@ export default {
|
||||
'Tandatangan saya unik seperti personaliti saya. Bahagian ini akan memaparkan ringkasan profil sebenar kemudian.',
|
||||
},
|
||||
message: {
|
||||
title: 'Mesej',
|
||||
title: TEXT_SITE_MESSAGES,
|
||||
back: 'Kembali',
|
||||
loading: 'Memuatkan mesej...',
|
||||
loadFailed: 'Gagal memuatkan mesej. Sila cuba lagi kemudian.',
|
||||
@@ -180,7 +253,7 @@ export default {
|
||||
unread: 'Belum dibaca',
|
||||
eventBonus:
|
||||
'[Acara Bonus Tambah Nilai] Dari 1 Oktober hingga 7 Oktober 2026, tuntut ganjaran rebat anda...',
|
||||
check: 'Semak',
|
||||
check: TEXT_VIEW,
|
||||
deleteRecords: 'Padam rekod',
|
||||
},
|
||||
financeRecords: {
|
||||
@@ -192,9 +265,9 @@ export default {
|
||||
loading: 'Memuatkan rekod...',
|
||||
loadFailed: 'Gagal memuatkan rekod. Sila cuba lagi kemudian.',
|
||||
empty: 'Belum ada rekod',
|
||||
page: 'Halaman {{page}} / jumlah {{total}}',
|
||||
previous: 'Sebelumnya',
|
||||
next: 'Seterusnya',
|
||||
page: TEXT_PAGE_INDICATOR,
|
||||
previous: TEXT_PREV_PAGE,
|
||||
next: TEXT_NEXT_PAGE,
|
||||
},
|
||||
walletRecords: {
|
||||
amount: 'Jumlah',
|
||||
@@ -203,12 +276,12 @@ export default {
|
||||
empty: 'Belum ada rekod dompet',
|
||||
loadFailed: 'Gagal memuatkan rekod dompet. Sila cuba lagi kemudian.',
|
||||
loading: 'Memuatkan rekod dompet...',
|
||||
next: 'Seterusnya',
|
||||
page: 'Halaman {{page}} / jumlah {{total}}',
|
||||
previous: 'Sebelumnya',
|
||||
next: TEXT_NEXT_PAGE,
|
||||
page: TEXT_PAGE_INDICATOR,
|
||||
previous: TEXT_PREV_PAGE,
|
||||
remark: 'Catatan',
|
||||
time: 'Masa',
|
||||
type: 'Rekod Dompet',
|
||||
time: TEXT_TIME,
|
||||
type: TEXT_WALLET_RECORDS,
|
||||
},
|
||||
},
|
||||
withdrawTopup: {
|
||||
@@ -240,8 +313,8 @@ export default {
|
||||
dialog: {
|
||||
close: 'Tutup notifikasi',
|
||||
confirm: 'OK',
|
||||
no: 'Tidak',
|
||||
yes: 'Ya',
|
||||
no: TEXT_NO,
|
||||
yes: TEXT_YES,
|
||||
},
|
||||
modal: {
|
||||
close: 'Tutup modal',
|
||||
@@ -259,7 +332,7 @@ export default {
|
||||
inviteLinkCopyFailed:
|
||||
'Gagal menyalin pautan jemputan. Sila salin secara manual.',
|
||||
insufficientBalance: 'Baki tidak mencukupi. Sila laraskan taruhan.',
|
||||
betLimitExceeded: 'Melebihi had taruhan tunggal',
|
||||
betLimitExceeded: TEXT_BET_LIMIT_EXCEEDED,
|
||||
betUnavailable: 'Taruhan tidak tersedia untuk pusingan ini',
|
||||
betPlaced: 'Taruhan berjaya dihantar',
|
||||
noRecentSuccessfulBet:
|
||||
@@ -287,7 +360,7 @@ export default {
|
||||
common: {
|
||||
arrowIconAlt: 'Anak panah',
|
||||
actions: {
|
||||
submitting: 'Menghantar...',
|
||||
submitting: TEXT_SUBMITTING,
|
||||
},
|
||||
passwordVisibility: {
|
||||
hide: 'Sembunyikan kata laluan',
|
||||
@@ -296,16 +369,16 @@ export default {
|
||||
},
|
||||
login: {
|
||||
actions: {
|
||||
submit: 'Log Masuk',
|
||||
submit: TEXT_LOGIN,
|
||||
},
|
||||
fields: {
|
||||
username: {
|
||||
label: 'Nombor Telefon:',
|
||||
placeholder: 'Masukkan nombor telefon',
|
||||
label: TEXT_MOBILE_LABEL,
|
||||
placeholder: TEXT_MOBILE_PLACEHOLDER,
|
||||
},
|
||||
password: {
|
||||
label: 'Kata Laluan:',
|
||||
placeholder: 'Masukkan kata laluan',
|
||||
label: TEXT_PASSWORD_LABEL,
|
||||
placeholder: TEXT_PASSWORD_PLACEHOLDER,
|
||||
},
|
||||
},
|
||||
footer: {
|
||||
@@ -319,20 +392,20 @@ export default {
|
||||
},
|
||||
register: {
|
||||
actions: {
|
||||
submit: 'Daftar',
|
||||
submit: TEXT_REGISTER,
|
||||
},
|
||||
fields: {
|
||||
mobile: {
|
||||
label: 'Nombor Telefon:',
|
||||
placeholder: 'Masukkan nombor telefon',
|
||||
label: TEXT_MOBILE_LABEL,
|
||||
placeholder: TEXT_MOBILE_PLACEHOLDER,
|
||||
},
|
||||
captcha: {
|
||||
label: 'Kod:',
|
||||
placeholder: 'Masukkan kod pengesahan',
|
||||
},
|
||||
password: {
|
||||
label: 'Kata Laluan:',
|
||||
placeholder: 'Masukkan kata laluan',
|
||||
label: TEXT_PASSWORD_LABEL,
|
||||
placeholder: TEXT_PASSWORD_PLACEHOLDER,
|
||||
},
|
||||
confirmPassword: {
|
||||
label: 'Sahkan Kata Laluan:',
|
||||
@@ -357,7 +430,7 @@ export default {
|
||||
submitFailed: 'Gagal menghantar kod. Sila cuba lagi kemudian.',
|
||||
},
|
||||
send: 'Dapatkan kod',
|
||||
sending: 'Menghantar...',
|
||||
sending: TEXT_SUBMITTING,
|
||||
success: 'Kod pengesahan telah dihantar.',
|
||||
},
|
||||
},
|
||||
@@ -394,27 +467,27 @@ export default {
|
||||
header: {
|
||||
systemTime: 'Masa Sistem',
|
||||
rules: 'Peraturan',
|
||||
message: 'Mesej',
|
||||
message: TEXT_SITE_MESSAGES,
|
||||
bgm: 'BGM',
|
||||
id: 'ID',
|
||||
fullscreen: 'Skrin',
|
||||
login: 'Log Masuk',
|
||||
register: 'Daftar',
|
||||
login: TEXT_LOGIN,
|
||||
register: TEXT_REGISTER,
|
||||
},
|
||||
control: {
|
||||
trend: 'Trend',
|
||||
map: 'Peta',
|
||||
selected: 'Dipilih',
|
||||
totalBet: 'Jumlah Taruhan',
|
||||
confirm: 'Sahkan',
|
||||
confirm: TEXT_CONFIRM,
|
||||
selectNumbers: 'Pilih Nombor',
|
||||
insufficientBalance: 'Baki Tidak Mencukupi',
|
||||
betLimitExceeded: 'Melebihi Had',
|
||||
submitting: 'Menghantar...',
|
||||
submitting: TEXT_SUBMITTING,
|
||||
actions: {
|
||||
clear: 'Kosongkan',
|
||||
repeat: 'Ulang',
|
||||
'auto-spin': 'Putaran Auto',
|
||||
'auto-spin': TEXT_AUTO_HOSTING,
|
||||
},
|
||||
},
|
||||
status: {
|
||||
@@ -428,7 +501,7 @@ export default {
|
||||
description: '(Menerima Taruhan)',
|
||||
},
|
||||
locked: {
|
||||
label: 'Dikunci',
|
||||
label: TEXT_LOCKED,
|
||||
description: '(Taruhan Ditutup)',
|
||||
},
|
||||
revealing: {
|
||||
@@ -450,7 +523,7 @@ export default {
|
||||
},
|
||||
animal: {
|
||||
insufficientBalanceRecharge: 'Baki tidak mencukupi, sila tambah nilai',
|
||||
betLimitExceeded: 'Melebihi had taruhan tunggal',
|
||||
betLimitExceeded: TEXT_BET_LIMIT_EXCEEDED,
|
||||
loading: 'Memuatkan',
|
||||
selectionLimitReached: 'Melebihi pilihan aksara yang dibenarkan',
|
||||
tapToEnter: 'Ketik Untuk Masuk',
|
||||
@@ -465,29 +538,29 @@ export default {
|
||||
orderNo: 'No. Pesanan',
|
||||
roundId: 'ID Pusingan',
|
||||
numbers: 'Nombor Pertaruhan',
|
||||
createdAt: 'Masa',
|
||||
createdAt: TEXT_TIME,
|
||||
settledAt: 'Masa Selesai',
|
||||
totalPoolAmount: 'Jumlah Pertaruhan',
|
||||
winningResult: 'Keputusan Menang',
|
||||
payout: 'Jumlah Menang',
|
||||
empty: 'Belum ada sejarah',
|
||||
end: 'Tiada lagi rekod',
|
||||
loading: 'Memuatkan...',
|
||||
settled: 'Selesai',
|
||||
loading: TEXT_LOADING,
|
||||
settled: TEXT_SETTLED,
|
||||
},
|
||||
periodHistory: {
|
||||
title: 'Sejarah Keputusan Cabutan',
|
||||
close: 'Tutup sejarah keputusan cabutan',
|
||||
empty: 'Belum ada keputusan cabutan',
|
||||
failed: 'Gagal memuatkan keputusan cabutan',
|
||||
loading: 'Memuatkan...',
|
||||
loading: TEXT_LOADING,
|
||||
retry: 'Cuba lagi',
|
||||
},
|
||||
topup: {
|
||||
title: 'Konfigurasi Tambah Nilai',
|
||||
platformCoinLabel: 'Syiling Platform',
|
||||
currencyLabel: 'Jenis Mata Wang',
|
||||
channelLabel: 'Saluran Pembayaran',
|
||||
currencyLabel: TEXT_CURRENCY_TYPE,
|
||||
channelLabel: TEXT_PAYMENT_CHANNEL,
|
||||
rateHint:
|
||||
'Kadar pertukaran hanya untuk rujukan. Jumlah akhir tertakluk kepada kadar semasa tambah nilai.',
|
||||
tier: {
|
||||
@@ -529,13 +602,13 @@ export default {
|
||||
feeNotice:
|
||||
'Transaksi antara RM10 dan RM99.99 akan dikenakan yuran pengeluaran minimum RM 1.',
|
||||
cancel: 'Batal',
|
||||
confirm: 'Sahkan',
|
||||
confirm: TEXT_CONFIRM,
|
||||
submitSuccess: 'Permohonan pengeluaran telah dihantar',
|
||||
withdrawal: 'Pengeluaran',
|
||||
fields: {
|
||||
diamondAmount: 'Jumlah Berlian Pengeluaran',
|
||||
currencyType: 'Jenis Mata Wang',
|
||||
paymentChannel: 'Saluran Pembayaran',
|
||||
currencyType: TEXT_CURRENCY_TYPE,
|
||||
paymentChannel: TEXT_PAYMENT_CHANNEL,
|
||||
bankCode: 'Kod Bank',
|
||||
cardHolderName: 'Nama Pemegang Kad',
|
||||
bankAccountNumber: 'Nombor Akaun Bank',
|
||||
|
||||
@@ -1,3 +1,76 @@
|
||||
/* 以下为多语言中重复出现的文案,统一声明一次后在下方各 key 复用,避免同一文案多处声明。 */
|
||||
/** @description 登录的统一文案。 */
|
||||
const TEXT_LOGIN = '登录'
|
||||
|
||||
/** @description 注册的统一文案。 */
|
||||
const TEXT_REGISTER = '注册'
|
||||
|
||||
/** @description “是”的统一文案。 */
|
||||
const TEXT_YES = '是'
|
||||
|
||||
/** @description “否”的统一文案。 */
|
||||
const TEXT_NO = '否'
|
||||
|
||||
/** @description “查看”的统一文案。 */
|
||||
const TEXT_VIEW = '查看'
|
||||
|
||||
/** @description 自动托管的统一文案。 */
|
||||
const TEXT_AUTO_HOSTING = '自动托管'
|
||||
|
||||
/** @description 钱包流水的统一文案。 */
|
||||
const TEXT_WALLET_RECORDS = '钱包流水'
|
||||
|
||||
/** @description 站内消息的统一文案。 */
|
||||
const TEXT_SITE_MESSAGES = '站内消息'
|
||||
|
||||
/** @description 分页“第x页/共x条”的统一文案。 */
|
||||
const TEXT_PAGE_INDICATOR = '第 {{page}} 页 / 共 {{total}} 条'
|
||||
|
||||
/** @description 上一页的统一文案。 */
|
||||
const TEXT_PREV_PAGE = '上一页'
|
||||
|
||||
/** @description 下一页的统一文案。 */
|
||||
const TEXT_NEXT_PAGE = '下一页'
|
||||
|
||||
/** @description “时间”的统一文案。 */
|
||||
const TEXT_TIME = '时间'
|
||||
|
||||
/** @description 超过单次投注限额的统一文案。 */
|
||||
const TEXT_BET_LIMIT_EXCEEDED = '超过单次投注限额'
|
||||
|
||||
/** @description “提交中...”的统一文案。 */
|
||||
const TEXT_SUBMITTING = '提交中...'
|
||||
|
||||
/** @description 手机号字段标签的统一文案。 */
|
||||
const TEXT_MOBILE_LABEL = '手机号:'
|
||||
|
||||
/** @description 手机号输入占位的统一文案。 */
|
||||
const TEXT_MOBILE_PLACEHOLDER = '请输入手机号'
|
||||
|
||||
/** @description 密码字段标签的统一文案。 */
|
||||
const TEXT_PASSWORD_LABEL = '密码:'
|
||||
|
||||
/** @description 密码输入占位的统一文案。 */
|
||||
const TEXT_PASSWORD_PLACEHOLDER = '请输入密码'
|
||||
|
||||
/** @description “确认”按钮的统一文案。 */
|
||||
const TEXT_CONFIRM = '确认'
|
||||
|
||||
/** @description “加载中...”的统一文案。 */
|
||||
const TEXT_LOADING = '加载中...'
|
||||
|
||||
/** @description 货币类型的统一文案。 */
|
||||
const TEXT_CURRENCY_TYPE = '货币类型'
|
||||
|
||||
/** @description 支付渠道的统一文案。 */
|
||||
const TEXT_PAYMENT_CHANNEL = '支付渠道'
|
||||
|
||||
/** @description “已封盘”的统一文案。 */
|
||||
const TEXT_LOCKED = '已封盘'
|
||||
|
||||
/** @description “已结算”的统一文案。 */
|
||||
const TEXT_SETTLED = '已结算'
|
||||
|
||||
export default {
|
||||
nav: {
|
||||
home: '首页',
|
||||
@@ -86,8 +159,8 @@ export default {
|
||||
},
|
||||
phases: {
|
||||
betting: '下注中',
|
||||
locked: '已封盘',
|
||||
settled: '已结算',
|
||||
locked: TEXT_LOCKED,
|
||||
settled: TEXT_SETTLED,
|
||||
},
|
||||
roundBettingStart: {
|
||||
title: '{{roundId}}期',
|
||||
@@ -97,8 +170,8 @@ export default {
|
||||
unifiedBetHint: '统一下注额',
|
||||
totalBet: '总下注',
|
||||
canBet: '可下注',
|
||||
yes: '是',
|
||||
no: '否',
|
||||
yes: TEXT_YES,
|
||||
no: TEXT_NO,
|
||||
quickBet: '快速选中 08',
|
||||
clearPending: '清空未确认',
|
||||
autoModeDemo: '自动托管演示',
|
||||
@@ -106,16 +179,16 @@ export default {
|
||||
},
|
||||
modals: {
|
||||
login: {
|
||||
title: '登录',
|
||||
title: TEXT_LOGIN,
|
||||
},
|
||||
register: {
|
||||
title: '注册',
|
||||
title: TEXT_REGISTER,
|
||||
},
|
||||
notice: {
|
||||
title: '活动公告',
|
||||
content:
|
||||
'这里后续将接入真实的活动公告内容、图文素材和滚动阅读区域。当前版本先用多语言结构完成桌面端公告弹窗能力。',
|
||||
check: '查看',
|
||||
check: TEXT_VIEW,
|
||||
},
|
||||
entryNotice: {
|
||||
title: '网站公告',
|
||||
@@ -139,7 +212,7 @@ export default {
|
||||
topup: '充值',
|
||||
},
|
||||
autoSetting: {
|
||||
title: '自动托管',
|
||||
title: TEXT_AUTO_HOSTING,
|
||||
startAutoSpin: '开始自动托管',
|
||||
rows: {
|
||||
stopIfBalanceLowerThan: '余额低于时停止',
|
||||
@@ -152,8 +225,8 @@ export default {
|
||||
tabs: {
|
||||
profile: '个人信息',
|
||||
financeRecords: '充值/提现记录',
|
||||
walletRecords: '钱包流水',
|
||||
message: '站内消息',
|
||||
walletRecords: TEXT_WALLET_RECORDS,
|
||||
message: TEXT_SITE_MESSAGES,
|
||||
},
|
||||
profile: {
|
||||
name: '用户名',
|
||||
@@ -165,7 +238,7 @@ export default {
|
||||
signature: '我的个性签名和灵魂一样独特,后续这里会接真实资料字段。',
|
||||
},
|
||||
message: {
|
||||
title: '站内消息',
|
||||
title: TEXT_SITE_MESSAGES,
|
||||
back: '返回',
|
||||
loading: '消息加载中...',
|
||||
loadFailed: '消息加载失败,请稍后重试',
|
||||
@@ -173,7 +246,7 @@ export default {
|
||||
read: '已读',
|
||||
unread: '未读',
|
||||
eventBonus: '[充值活动] 10 月 1 日至 10 月 7 日期间可获得返利奖励……',
|
||||
check: '查看',
|
||||
check: TEXT_VIEW,
|
||||
deleteRecords: '删除记录',
|
||||
},
|
||||
financeRecords: {
|
||||
@@ -185,9 +258,9 @@ export default {
|
||||
loading: '记录加载中...',
|
||||
loadFailed: '记录加载失败,请稍后重试',
|
||||
empty: '暂无记录',
|
||||
page: '第 {{page}} 页 / 共 {{total}} 条',
|
||||
previous: '上一页',
|
||||
next: '下一页',
|
||||
page: TEXT_PAGE_INDICATOR,
|
||||
previous: TEXT_PREV_PAGE,
|
||||
next: TEXT_NEXT_PAGE,
|
||||
},
|
||||
walletRecords: {
|
||||
amount: '变动金额',
|
||||
@@ -196,12 +269,12 @@ export default {
|
||||
empty: '暂无钱包流水',
|
||||
loadFailed: '钱包流水加载失败,请稍后重试',
|
||||
loading: '钱包流水加载中...',
|
||||
next: '下一页',
|
||||
page: '第 {{page}} 页 / 共 {{total}} 条',
|
||||
previous: '上一页',
|
||||
next: TEXT_NEXT_PAGE,
|
||||
page: TEXT_PAGE_INDICATOR,
|
||||
previous: TEXT_PREV_PAGE,
|
||||
remark: '备注',
|
||||
time: '时间',
|
||||
type: '钱包流水',
|
||||
time: TEXT_TIME,
|
||||
type: TEXT_WALLET_RECORDS,
|
||||
},
|
||||
},
|
||||
withdrawTopup: {
|
||||
@@ -210,7 +283,7 @@ export default {
|
||||
},
|
||||
},
|
||||
autoSpin: {
|
||||
eyebrow: '自动托管',
|
||||
eyebrow: TEXT_AUTO_HOSTING,
|
||||
title: '自动托管运行中',
|
||||
description: '托管态会覆盖主盘面,但目标格子和进度信息仍然保留可见。',
|
||||
runningRounds: '游戏托管中,已经进行 {{count}} 局',
|
||||
@@ -232,8 +305,8 @@ export default {
|
||||
dialog: {
|
||||
close: '关闭提示',
|
||||
confirm: '知道了',
|
||||
no: '否',
|
||||
yes: '是',
|
||||
no: TEXT_NO,
|
||||
yes: TEXT_YES,
|
||||
},
|
||||
modal: {
|
||||
close: '关闭弹窗',
|
||||
@@ -249,7 +322,7 @@ export default {
|
||||
inviteLinkCopied: '邀请链接已复制',
|
||||
inviteLinkCopyFailed: '邀请链接复制失败,请手动复制',
|
||||
insufficientBalance: '余额不足,请调整下注金额',
|
||||
betLimitExceeded: '超过单次投注限额',
|
||||
betLimitExceeded: TEXT_BET_LIMIT_EXCEEDED,
|
||||
betUnavailable: '当前期不可下注',
|
||||
betPlaced: '下注成功',
|
||||
noRecentSuccessfulBet: '暂无上一局成功下注记录',
|
||||
@@ -270,7 +343,7 @@ export default {
|
||||
common: {
|
||||
arrowIconAlt: '箭头',
|
||||
actions: {
|
||||
submitting: '提交中...',
|
||||
submitting: TEXT_SUBMITTING,
|
||||
},
|
||||
passwordVisibility: {
|
||||
hide: '隐藏密码',
|
||||
@@ -279,16 +352,16 @@ export default {
|
||||
},
|
||||
login: {
|
||||
actions: {
|
||||
submit: '登录',
|
||||
submit: TEXT_LOGIN,
|
||||
},
|
||||
fields: {
|
||||
username: {
|
||||
label: '手机号:',
|
||||
placeholder: '请输入手机号',
|
||||
label: TEXT_MOBILE_LABEL,
|
||||
placeholder: TEXT_MOBILE_PLACEHOLDER,
|
||||
},
|
||||
password: {
|
||||
label: '密码:',
|
||||
placeholder: '请输入密码',
|
||||
label: TEXT_PASSWORD_LABEL,
|
||||
placeholder: TEXT_PASSWORD_PLACEHOLDER,
|
||||
},
|
||||
},
|
||||
footer: {
|
||||
@@ -302,20 +375,20 @@ export default {
|
||||
},
|
||||
register: {
|
||||
actions: {
|
||||
submit: '注册',
|
||||
submit: TEXT_REGISTER,
|
||||
},
|
||||
fields: {
|
||||
mobile: {
|
||||
label: '手机号:',
|
||||
placeholder: '请输入手机号',
|
||||
label: TEXT_MOBILE_LABEL,
|
||||
placeholder: TEXT_MOBILE_PLACEHOLDER,
|
||||
},
|
||||
captcha: {
|
||||
label: '验证码:',
|
||||
placeholder: '请输入验证码',
|
||||
},
|
||||
password: {
|
||||
label: '密码:',
|
||||
placeholder: '请输入密码',
|
||||
label: TEXT_PASSWORD_LABEL,
|
||||
placeholder: TEXT_PASSWORD_PLACEHOLDER,
|
||||
},
|
||||
confirmPassword: {
|
||||
label: '确认密码:',
|
||||
@@ -349,7 +422,7 @@ export default {
|
||||
required: '请输入验证码',
|
||||
},
|
||||
username: {
|
||||
required: '请输入手机号',
|
||||
required: TEXT_MOBILE_PLACEHOLDER,
|
||||
invalidPhone: '请输入正确的手机号',
|
||||
},
|
||||
password: {
|
||||
@@ -379,23 +452,23 @@ export default {
|
||||
bgm: '音乐',
|
||||
id: '编号',
|
||||
fullscreen: '全屏',
|
||||
login: '登录',
|
||||
register: '注册',
|
||||
login: TEXT_LOGIN,
|
||||
register: TEXT_REGISTER,
|
||||
},
|
||||
control: {
|
||||
trend: '走势',
|
||||
map: '地图',
|
||||
selected: '已选',
|
||||
totalBet: '总下注',
|
||||
confirm: '确认',
|
||||
confirm: TEXT_CONFIRM,
|
||||
selectNumbers: '请选择号码',
|
||||
insufficientBalance: '余额不足',
|
||||
betLimitExceeded: '超过限额',
|
||||
submitting: '提交中...',
|
||||
submitting: TEXT_SUBMITTING,
|
||||
actions: {
|
||||
clear: '清空',
|
||||
repeat: '重复',
|
||||
'auto-spin': '自动托管',
|
||||
'auto-spin': TEXT_AUTO_HOSTING,
|
||||
},
|
||||
},
|
||||
status: {
|
||||
@@ -409,7 +482,7 @@ export default {
|
||||
description: '(接受下注)',
|
||||
},
|
||||
locked: {
|
||||
label: '已封盘',
|
||||
label: TEXT_LOCKED,
|
||||
description: '(停止下注)',
|
||||
},
|
||||
revealing: {
|
||||
@@ -431,7 +504,7 @@ export default {
|
||||
},
|
||||
animal: {
|
||||
insufficientBalanceRecharge: '余额不足,请充值',
|
||||
betLimitExceeded: '超过单次投注限额',
|
||||
betLimitExceeded: TEXT_BET_LIMIT_EXCEEDED,
|
||||
loading: '加载中',
|
||||
selectionLimitReached: '超过可选择字花',
|
||||
tapToEnter: '点击进入',
|
||||
@@ -446,29 +519,29 @@ export default {
|
||||
orderNo: '订单号',
|
||||
roundId: '期号',
|
||||
numbers: '下注号码',
|
||||
createdAt: '时间',
|
||||
createdAt: TEXT_TIME,
|
||||
settledAt: '结算时间',
|
||||
totalPoolAmount: '下注金额',
|
||||
winningResult: '中奖字花',
|
||||
payout: '中奖金额',
|
||||
empty: '暂无历史记录',
|
||||
end: '没有更多记录了',
|
||||
loading: '加载中...',
|
||||
settled: '已结算',
|
||||
loading: TEXT_LOADING,
|
||||
settled: TEXT_SETTLED,
|
||||
},
|
||||
periodHistory: {
|
||||
title: '开奖结果历史',
|
||||
close: '关闭开奖结果历史',
|
||||
empty: '暂无开奖结果',
|
||||
failed: '开奖结果加载失败',
|
||||
loading: '加载中...',
|
||||
loading: TEXT_LOADING,
|
||||
retry: '重试',
|
||||
},
|
||||
topup: {
|
||||
title: '充值配置',
|
||||
platformCoinLabel: '平台币',
|
||||
currencyLabel: '货币类型',
|
||||
channelLabel: '支付渠道',
|
||||
currencyLabel: TEXT_CURRENCY_TYPE,
|
||||
channelLabel: TEXT_PAYMENT_CHANNEL,
|
||||
rateHint: '汇率为参考价格,实际以充值时为准。',
|
||||
tier: {
|
||||
bonus: '赠送',
|
||||
@@ -506,13 +579,13 @@ export default {
|
||||
notice: '注意',
|
||||
feeNotice: 'RM10 - RM99.99 之间的交易将收取最低RM 1的提现手续费',
|
||||
cancel: '取消',
|
||||
confirm: '确认',
|
||||
confirm: TEXT_CONFIRM,
|
||||
submitSuccess: '提现申请已提交',
|
||||
withdrawal: '提现',
|
||||
fields: {
|
||||
diamondAmount: '提现钻石数量',
|
||||
currencyType: '货币类型',
|
||||
paymentChannel: '支付渠道',
|
||||
currencyType: TEXT_CURRENCY_TYPE,
|
||||
paymentChannel: TEXT_PAYMENT_CHANNEL,
|
||||
bankCode: '银行代码',
|
||||
cardHolderName: '持卡人姓名',
|
||||
bankAccountNumber: '银行账号',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { create } from 'zustand'
|
||||
|
||||
import { AUTO_HOSTING_DEFAULT_SINGLE_WIN_THRESHOLD } from '@/constants'
|
||||
import type { BetSelection } from '@/features/game/shared'
|
||||
|
||||
export interface AutoHostingStopRules {
|
||||
@@ -44,7 +45,7 @@ const DEFAULT_AUTO_HOSTING_RULES: AutoHostingStopRules = {
|
||||
enabled: false,
|
||||
},
|
||||
stopIfSingleWinAbove: {
|
||||
amount: 50_000,
|
||||
amount: AUTO_HOSTING_DEFAULT_SINGLE_WIN_THRESHOLD,
|
||||
enabled: false,
|
||||
},
|
||||
stopOnJackpot: false,
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { create } from 'zustand'
|
||||
|
||||
import {
|
||||
CONNECTION_LATENCY_FAIR_MS,
|
||||
MAX_JACKPOT_BROADCAST_COUNT,
|
||||
} from '@/constants'
|
||||
import type {
|
||||
AnnouncementState,
|
||||
ConnectionState,
|
||||
@@ -18,8 +22,6 @@ type GameSessionSlice = Pick<
|
||||
'announcements' | 'connection' | 'dashboard'
|
||||
>
|
||||
|
||||
const MAX_JACKPOT_BROADCAST_COUNT = 20
|
||||
|
||||
export interface JackpotBroadcastItem {
|
||||
id: string
|
||||
message: string
|
||||
@@ -173,7 +175,8 @@ export const selectActiveAnnouncement = (state: GameSessionStoreState) =>
|
||||
|
||||
export const selectIsConnectionHealthy = (state: GameSessionStoreState) =>
|
||||
state.connection.status === 'connected' &&
|
||||
(state.connection.latencyMs === null || state.connection.latencyMs < 150)
|
||||
(state.connection.latencyMs === null ||
|
||||
state.connection.latencyMs < CONNECTION_LATENCY_FAIR_MS)
|
||||
|
||||
export const selectUnreadAnnouncementCount = (state: GameSessionStoreState) =>
|
||||
getUnreadAnnouncementCount(state.announcements)
|
||||
|
||||
Reference in New Issue
Block a user