Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 20 additions & 20 deletions src/components/common/theme-demo.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,32 @@ import { LoadingSpinner } from './loading-spinner';
import { EmptyState } from './empty-state';

function ThemeDemoComponent() {
const { t, i18n } = useTranslation();
const { t, i18n } = useTranslation(['common', 'wallet', 'transaction']);

return (
<div className="bg-card space-y-6 rounded-xl p-4">
<div className="space-y-2">
<h2 className="text-lg font-semibold">{t('wallet.myWallet')}</h2>
<p className="text-muted-foreground text-sm">{t('common.loading')}</p>
<h2 className="text-lg font-semibold">{t('wallet:myWallet')}</h2>
<p className="text-muted-foreground text-sm">{t('loading')}</p>
</div>

<div className="flex items-center gap-3">
<LoadingSpinner size="sm" />
<span className="text-sm">{t('common.loading')}...</span>
<span className="text-sm">{t('loading')}...</span>
</div>

<div className="flex gap-2">
<GradientButton variant="purple" size="sm">
{t('wallet.transfer')}
{t('wallet:transfer')}
</GradientButton>
<GradientButton variant="blue" size="sm">
{t('wallet.receive')}
{t('wallet:receive')}
</GradientButton>
</div>

<div className="bg-background rounded-lg p-3">
<p className="text-sm font-medium">{t('transaction.send')}</p>
<p className="text-muted-foreground text-xs">{t('transaction.pending')}</p>
<p className="text-sm font-medium">{t('transaction:send')}</p>
<p className="text-muted-foreground text-xs">{t('transaction:pending')}</p>
</div>

<div className="text-muted-foreground text-xs">
Expand Down Expand Up @@ -72,14 +72,14 @@ export const Default: Story = {};

export const WithEmptyState: Story = {
render: () => {
const { t } = useTranslation();
const { t } = useTranslation(['token', 'empty']);
return (
<EmptyState
title={t('token.noAssets')}
description={t('empty.description')}
title={t('noAssets')}
description={t('empty:description')}
action={
<GradientButton variant="purple" size="sm">
{t('token.addToken')}
{t('addToken')}
</GradientButton>
}
/>
Expand All @@ -89,13 +89,13 @@ export const WithEmptyState: Story = {

export const TransactionStates: Story = {
render: () => {
const { t } = useTranslation();
const { t } = useTranslation('transaction');
return (
<div className="space-y-3">
{(['send', 'receive', 'swap', 'stake'] as const).map((type) => (
<div key={type} className="bg-card flex items-center justify-between rounded-lg p-3">
<span className="font-medium">{t(`transaction.${type}`)}</span>
<span className="text-muted-foreground text-sm">{t('transaction.confirmed')}</span>
<span className="font-medium">{t(type)}</span>
<span className="text-muted-foreground text-sm">{t('confirmed')}</span>
</div>
))}
</div>
Expand All @@ -105,11 +105,11 @@ export const TransactionStates: Story = {

export const SecurityLabels: Story = {
render: () => {
const { t } = useTranslation();
const { t } = useTranslation('security');
return (
<div className="space-y-3">
<div className="bg-card rounded-lg p-3">
<p className="font-medium">{t('security.password')}</p>
<p className="font-medium">{t('password')}</p>
<div className="mt-2 flex gap-2">
{(['weak', 'medium', 'strong'] as const).map((level) => (
<span
Expand All @@ -122,14 +122,14 @@ export const SecurityLabels: Story = {
: 'bg-green-500/20 text-green-500'
}`}
>
{t(`security.strength.${level}`)}
{t(`strength.${level}`)}
</span>
))}
</div>
</div>
<div className="bg-card rounded-lg p-3">
<p className="font-medium">{t('security.mnemonic')}</p>
<p className="text-muted-foreground mt-1 text-sm">{t('security.copyMnemonic')}</p>
<p className="font-medium">{t('mnemonic')}</p>
<p className="text-muted-foreground mt-1 text-sm">{t('copyMnemonic')}</p>
</div>
</div>
);
Expand Down
10 changes: 5 additions & 5 deletions src/components/onboarding/key-type-selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ const OPTIONS: Array<{
];

export function KeyTypeSelector({ value, onChange, disabled = false, className }: KeyTypeSelectorProps) {
const { t } = useTranslation(['onboarding', 'common']);
const { t } = useTranslation('onboarding');

return (
<div className={cn('space-y-3', className)}>
<div className="text-sm font-medium">{t('onboarding:keyType.title')}</div>
<div className="text-sm font-medium">{t('keyType.title')}</div>

<div role="radiogroup" aria-label={t('onboarding:keyType.title')} className="grid gap-3">
<div role="radiogroup" aria-label={t('keyType.title')} className="grid gap-3">
{OPTIONS.map((option) => {
const isSelected = value === option.value;
return (
Expand All @@ -60,8 +60,8 @@ export function KeyTypeSelector({ value, onChange, disabled = false, className }
>
<div className="flex items-start justify-between gap-3">
<div className="min-w-0 flex-1">
<div className="text-sm font-semibold">{t(`onboarding:${option.titleKey}`)}</div>
<div className="text-muted-foreground mt-1 text-xs">{t(`onboarding:${option.descKey}`)}</div>
<div className="text-sm font-semibold">{t(option.titleKey as 'keyType.mnemonic' | 'keyType.arbitrary')}</div>
<div className="text-muted-foreground mt-1 text-xs">{t(option.descKey as 'keyType.mnemonicDesc' | 'keyType.arbitraryDesc')}</div>
<div className="mt-3 flex flex-wrap gap-2">
{option.tags.map((tag) => (
<span
Expand Down
12 changes: 6 additions & 6 deletions src/components/security/password-input.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { describe, it, expect, vi } from 'vitest'
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { PasswordInput, calculateStrength } from './password-input'
import { TestI18nProvider, testI18n } from '@/test/i18n-mock'
import { TestI18nProvider } from '@/test/i18n-mock'

function renderWithProviders(ui: React.ReactElement) {
return render(<TestI18nProvider>{ui}</TestI18nProvider>)
Expand Down Expand Up @@ -31,7 +31,7 @@ describe('calculateStrength', () => {
})

describe('PasswordInput', () => {
const placeholder = testI18n.t('security:passwordConfirm.placeholder')
const placeholder = '请输入密码'

it('renders password input', () => {
renderWithProviders(<PasswordInput placeholder={placeholder} />)
Expand All @@ -41,15 +41,15 @@ describe('PasswordInput', () => {
it('toggles password visibility', async () => {
renderWithProviders(<PasswordInput placeholder={placeholder} />)
const input = screen.getByPlaceholderText(placeholder)
const toggleButton = screen.getByRole('button', { name: testI18n.t('a11y.showPassword') })
const toggleButton = screen.getByRole('button', { name: '显示' })

expect(input).toHaveAttribute('type', 'password')

await userEvent.click(toggleButton)
expect(input).toHaveAttribute('type', 'text')
expect(screen.getByRole('button', { name: testI18n.t('a11y.hidePassword') })).toBeInTheDocument()
expect(screen.getByRole('button', { name: '隐藏' })).toBeInTheDocument()

await userEvent.click(screen.getByRole('button', { name: testI18n.t('a11y.hidePassword') }))
await userEvent.click(screen.getByRole('button', { name: '隐藏' }))
expect(input).toHaveAttribute('type', 'password')
})

Expand All @@ -58,7 +58,7 @@ describe('PasswordInput', () => {
const input = document.querySelector('input')!

await userEvent.type(input, 'test')
const strengthLabel = testI18n.t('common:passwordStrength')
const strengthLabel = '强度'
await waitFor(() => {
const matches = screen.getAllByText(
(_content, node) => node?.textContent?.includes(strengthLabel) ?? false,
Expand Down
4 changes: 2 additions & 2 deletions src/components/security/password-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const PasswordInput = forwardRef<HTMLInputElement, PasswordInputProps>(
const [visible, setVisible] = useState(false);
const [strength, setStrength] = useState<PasswordStrength>('weak');
const [hasValue, setHasValue] = useState(!!value);
const { t } = useTranslation();
const { t } = useTranslation('common');

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const inputValue = e.target.value;
Expand Down Expand Up @@ -95,7 +95,7 @@ const PasswordInput = forwardRef<HTMLInputElement, PasswordInputProps>(
<p className="text-muted-foreground text-xs">
<span className="sr-only">{t('a11y.passwordStrength', { strength: config.label })}</span>
<span aria-hidden="true">
{t('common:passwordStrength')}:
{t('passwordStrength')}:
<span
className={cn(
strength === 'weak' && 'text-destructive',
Expand Down
18 changes: 9 additions & 9 deletions src/components/security/pattern-lock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export function PatternLock({
size = 3,
'data-testid': testId,
}: PatternLockProps) {
const { t } = useTranslation();
const { t } = useTranslation('security');
const containerRef = useRef<HTMLDivElement>(null);
const svgRef = useRef<SVGSVGElement>(null);
const baseTestId = testId ?? undefined;
Expand Down Expand Up @@ -390,7 +390,7 @@ export function PatternLock({
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseLeave}
role="group"
aria-label={t('security:patternLock.gridLabel', { size })}
aria-label={t('patternLock.gridLabel', { size })}
>
{/* SVG 连线层 */}
<svg
Expand Down Expand Up @@ -478,7 +478,7 @@ export function PatternLock({
onChange={() => {}}
onKeyDown={(e) => handleNodeKeyDown(e, node.index)}
className="sr-only peer"
aria-label={t('security:patternLock.nodeLabel', {
aria-label={t('patternLock.nodeLabel', {
row: Math.floor(node.index / size) + 1,
col: (node.index % size) + 1,
order: isSelected ? orderIndex + 1 : undefined,
Expand All @@ -496,23 +496,23 @@ export function PatternLock({
<div className="text-center h-5">
{error || isErrorAnimating ? (
<p className="text-destructive text-sm">
{t('security:patternLock.error')}
{t('patternLock.error')}
</p>
) : selectedNodes.length === 0 ? (
<p className="text-muted-foreground text-sm">
{t('security:patternLock.hint', { min: minPoints })}
{t('patternLock.hint', { min: minPoints })}
</p>
) : selectedNodes.length < minPoints ? (
<p className="text-muted-foreground text-sm">
{t('security:patternLock.needMore', { current: selectedNodes.length, min: minPoints })}
{t('patternLock.needMore', { current: selectedNodes.length, min: minPoints })}
</p>
) : success ? (
<p className="text-primary text-sm font-medium">
{t('security:patternLock.success')}
{t('patternLock.success')}
</p>
) : (
<p className="text-primary text-sm">
{t('security:patternLock.valid', { count: selectedNodes.length })}
{t('patternLock.valid', { count: selectedNodes.length })}
</p>
)}
</div>
Expand All @@ -526,7 +526,7 @@ export function PatternLock({
data-testid={baseTestId ? `${baseTestId}-clear` : undefined}
className="mx-auto block text-sm text-muted-foreground hover:text-foreground transition-colors"
>
{t('security:patternLock.clear')}
{t('patternLock.clear')}
</button>
)}
</div>
Expand Down
10 changes: 5 additions & 5 deletions src/components/token/token-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ interface TokenItemProps {

export function TokenItem({ token, onClick, showChange = false, loading = false, className }: TokenItemProps) {
const isClickable = !!onClick;
const { t } = useTranslation();
const { t } = useTranslation(['currency', 'common']);
const currency = useCurrency();

const shouldFetchRate = token.fiatValue !== undefined && currency !== 'USD';
Expand All @@ -59,10 +59,10 @@ export function TokenItem({ token, onClick, showChange = false, loading = false,
const exchangeStatusMessage =
shouldFetchRate && !canConvert
? exchangeRateError
? t('currency:exchange.error')
? t('exchange.error')
: exchangeRateLoading
? t('currency:exchange.loading')
: t('currency:exchange.unavailable')
? t('exchange.loading')
: t('exchange.unavailable')
: null;

return (
Expand All @@ -71,7 +71,7 @@ export function TokenItem({ token, onClick, showChange = false, loading = false,
tabIndex={isClickable ? 0 : undefined}
onClick={onClick}
onKeyDown={isClickable ? (e) => e.key === 'Enter' && onClick?.() : undefined}
aria-label={isClickable ? t('a11y.tokenDetails', { token: token.symbol }) : undefined}
aria-label={isClickable ? t('common:a11y.tokenDetails', { token: token.symbol }) : undefined}
className={cn(
'@container flex items-center gap-3 rounded-xl p-3 transition-colors',
isClickable && 'hover:bg-muted/50 active:bg-muted cursor-pointer',
Expand Down
6 changes: 3 additions & 3 deletions src/components/transfer/address-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const AddressInput = forwardRef<HTMLInputElement, AddressInputProps>(
const [showDropdown, setShowDropdown] = useState(false);
const [selectedIndex, setSelectedIndex] = useState(-1);
const containerRef = useRef<HTMLDivElement>(null);
const { t } = useTranslation();
const { t } = useTranslation('common');
const errorId = useId();
const listboxId = useId();

Expand Down Expand Up @@ -149,7 +149,7 @@ const AddressInput = forwardRef<HTMLInputElement, AddressInputProps>(
onBlur={() => setTimeout(() => setFocused(false), 150)}
onKeyDown={handleKeyDown}
className="placeholder:text-muted-foreground min-w-0 flex-1 bg-transparent font-mono text-sm outline-none"
placeholder={t('common:addressPlaceholder')}
placeholder={t('addressPlaceholder')}
autoComplete="off"
autoCapitalize="off"
autoCorrect="off"
Expand Down Expand Up @@ -182,7 +182,7 @@ const AddressInput = forwardRef<HTMLInputElement, AddressInputProps>(
aria-label={t('a11y.paste')}
>
<ClipboardPaste className="size-5 @xs:hidden" />
<span className="hidden @xs:inline">{t('common:paste')}</span>
<span className="hidden @xs:inline">{t('paste')}</span>
</button>
</div>
</div>
Expand Down
6 changes: 3 additions & 3 deletions src/components/wallet/wallet-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function WalletCard({
onReceive,
className,
}: WalletCardProps) {
const { t } = useTranslation('wallet')
const { t } = useTranslation(['wallet', 'common'])
return (
<div className={cn('@container', className)}>
<div
Expand Down Expand Up @@ -85,7 +85,7 @@ export function WalletCard({
onClick={onTransfer}
className="flex-1 py-2 px-4 bg-white/20 rounded-full text-sm font-medium hover:bg-white/30 transition-colors @xs:py-2.5 @xs:text-base"
>
{t('common:transfer')}
{t('transfer')}
</button>
)}
{onReceive && (
Expand All @@ -94,7 +94,7 @@ export function WalletCard({
onClick={onReceive}
className="flex-1 py-2 px-4 bg-white/20 rounded-full text-sm font-medium hover:bg-white/30 transition-colors @xs:py-2.5 @xs:text-base"
>
{t('common:receive')}
{t('receive')}
</button>
)}
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/components/wallet/wallet-selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ function WalletItem({ wallet, isSelected, onSelect, notBackedUpLabel }: WalletIt
* Wallet selector component for switching between multiple wallets
*/
export function WalletSelector({ wallets, selectedId, onSelect, onClose, className }: WalletSelectorProps) {
const { t } = useTranslation('common');
const { t } = useTranslation(['common', 'wallet']);

const handleSelect = (wallet: WalletInfo) => {
onSelect?.(wallet);
Expand All @@ -97,7 +97,7 @@ export function WalletSelector({ wallets, selectedId, onSelect, onClose, classNa
wallet={wallet}
isSelected={wallet.id === selectedId}
onSelect={() => handleSelect(wallet)}
notBackedUpLabel={t('wallet:notBackedUp')}
notBackedUpLabel={t('notBackedUp')}
/>
))}
</div>
Expand Down
Loading