mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-04 11:30:58 +08:00
feat(i18n): add navigation internationalization
- Add Chinese/English translations for navigation buttons (实时/Real-time, 配置/Config, 看板/Dashboard) - Update HeaderBar component to use translation system for navigation elements - Add realtimeNav, configNav, dashboardNav translation keys - Support both desktop and mobile navigation internationalization 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: tinkle-community <tinklefund@gmail.com>
This commit is contained in:
@@ -1,56 +1,363 @@
|
||||
import { useState } from 'react'
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import { motion } from 'framer-motion'
|
||||
import { Menu, X } from 'lucide-react'
|
||||
import { Menu, X, ChevronDown } from 'lucide-react'
|
||||
import { t } from '../../i18n/translations'
|
||||
|
||||
export default function HeaderBar({ onLoginClick }: { onLoginClick: () => void }) {
|
||||
interface HeaderBarProps {
|
||||
onLoginClick: () => void
|
||||
isLoggedIn?: boolean
|
||||
isHomePage?: boolean
|
||||
currentPage?: string
|
||||
language?: string
|
||||
onLanguageChange?: (lang: string) => void
|
||||
user?: { email: string } | null
|
||||
onLogout?: () => void
|
||||
isAdminMode?: boolean
|
||||
onPageChange?: (page: string) => void
|
||||
}
|
||||
|
||||
export default function HeaderBar({ onLoginClick, isLoggedIn = false, isHomePage = false, currentPage, language = 'zh', onLanguageChange, user, onLogout, isAdminMode = false, onPageChange }: HeaderBarProps) {
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
||||
const [languageDropdownOpen, setLanguageDropdownOpen] = useState(false)
|
||||
const [userDropdownOpen, setUserDropdownOpen] = useState(false)
|
||||
const dropdownRef = useRef<HTMLDivElement>(null)
|
||||
const userDropdownRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
||||
setLanguageDropdownOpen(false)
|
||||
}
|
||||
if (userDropdownRef.current && !userDropdownRef.current.contains(event.target as Node)) {
|
||||
setUserDropdownOpen(false)
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside)
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<nav className='fixed top-0 w-full z-50 header-bar'>
|
||||
<div className='max-w-7xl mx-auto px-4 sm:px-6 lg:px-8'>
|
||||
<div className='flex items-center justify-between h-16'>
|
||||
{/* Logo */}
|
||||
<div className='flex items-center gap-3'>
|
||||
<img src='/images/logo.png' alt='NOFX Logo' className='w-8 h-8' />
|
||||
<a href='/' className='flex items-center gap-3 hover:opacity-80 transition-opacity cursor-pointer'>
|
||||
<img src='/icons/nofx.svg' alt='NOFX Logo' className='w-8 h-8' />
|
||||
<span className='text-xl font-bold' style={{ color: 'var(--brand-yellow)' }}>
|
||||
NOFX
|
||||
</span>
|
||||
<span className='text-sm hidden sm:block' style={{ color: 'var(--text-secondary)' }}>
|
||||
Agentic Trading OS
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
{/* Desktop Menu */}
|
||||
<div className='hidden md:flex items-center gap-6'>
|
||||
{['功能', '如何运作', 'GitHub', '社区'].map((item) => (
|
||||
<a
|
||||
key={item}
|
||||
href={
|
||||
item === 'GitHub'
|
||||
? 'https://github.com/tinkle-community/nofx'
|
||||
: item === '社区'
|
||||
? 'https://t.me/nofx_dev_community'
|
||||
: `#${item === '功能' ? 'features' : 'how-it-works'}`
|
||||
}
|
||||
target={item === 'GitHub' || item === '社区' ? '_blank' : undefined}
|
||||
rel={item === 'GitHub' || item === '社区' ? 'noopener noreferrer' : undefined}
|
||||
className='text-sm transition-colors relative group'
|
||||
style={{ color: 'var(--brand-light-gray)' }}
|
||||
>
|
||||
{item}
|
||||
<span
|
||||
className='absolute -bottom-1 left-0 w-0 h-0.5 group-hover:w-full transition-all duration-300'
|
||||
style={{ background: 'var(--brand-yellow)' }}
|
||||
/>
|
||||
</a>
|
||||
))}
|
||||
<button
|
||||
onClick={onLoginClick}
|
||||
className='px-4 py-2 rounded font-semibold text-sm'
|
||||
style={{ background: 'var(--brand-yellow)', color: 'var(--brand-black)' }}
|
||||
>
|
||||
登录 / 注册
|
||||
</button>
|
||||
<div className='hidden md:flex items-center justify-between flex-1 ml-8'>
|
||||
{/* Left Side - Navigation Tabs */}
|
||||
<div className='flex items-center gap-4'>
|
||||
{isLoggedIn ? (
|
||||
// Main app navigation when logged in
|
||||
<>
|
||||
<button
|
||||
onClick={() => {
|
||||
console.log('实时 button clicked, onPageChange:', onPageChange);
|
||||
onPageChange?.('competition');
|
||||
}}
|
||||
className='text-sm font-bold transition-all duration-300 relative focus:outline-2 focus:outline-yellow-500'
|
||||
style={{
|
||||
color: currentPage === 'competition' ? 'var(--brand-yellow)' : 'var(--brand-light-gray)',
|
||||
padding: '8px 16px',
|
||||
borderRadius: '8px',
|
||||
position: 'relative'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (currentPage !== 'competition') {
|
||||
e.currentTarget.style.color = 'var(--brand-yellow)';
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (currentPage !== 'competition') {
|
||||
e.currentTarget.style.color = 'var(--brand-light-gray)';
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* Background for selected state */}
|
||||
{currentPage === 'competition' && (
|
||||
<span
|
||||
className="absolute inset-0 rounded-lg"
|
||||
style={{
|
||||
background: 'rgba(240, 185, 11, 0.15)',
|
||||
zIndex: -1
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{t('realtimeNav', language)}
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
console.log('配置 button clicked, onPageChange:', onPageChange);
|
||||
onPageChange?.('traders');
|
||||
}}
|
||||
className='text-sm font-bold transition-all duration-300 relative focus:outline-2 focus:outline-yellow-500'
|
||||
style={{
|
||||
color: currentPage === 'traders' ? 'var(--brand-yellow)' : 'var(--brand-light-gray)',
|
||||
padding: '8px 16px',
|
||||
borderRadius: '8px',
|
||||
position: 'relative'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (currentPage !== 'traders') {
|
||||
e.currentTarget.style.color = 'var(--brand-yellow)';
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (currentPage !== 'traders') {
|
||||
e.currentTarget.style.color = 'var(--brand-light-gray)';
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* Background for selected state */}
|
||||
{currentPage === 'traders' && (
|
||||
<span
|
||||
className="absolute inset-0 rounded-lg"
|
||||
style={{
|
||||
background: 'rgba(240, 185, 11, 0.15)',
|
||||
zIndex: -1
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{t('configNav', language)}
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
console.log('看板 button clicked, onPageChange:', onPageChange);
|
||||
onPageChange?.('trader');
|
||||
}}
|
||||
className='text-sm font-bold transition-all duration-300 relative focus:outline-2 focus:outline-yellow-500'
|
||||
style={{
|
||||
color: currentPage === 'trader' ? 'var(--brand-yellow)' : 'var(--brand-light-gray)',
|
||||
padding: '8px 16px',
|
||||
borderRadius: '8px',
|
||||
position: 'relative'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (currentPage !== 'trader') {
|
||||
e.currentTarget.style.color = 'var(--brand-yellow)';
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (currentPage !== 'trader') {
|
||||
e.currentTarget.style.color = 'var(--brand-light-gray)';
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* Background for selected state */}
|
||||
{currentPage === 'trader' && (
|
||||
<span
|
||||
className="absolute inset-0 rounded-lg"
|
||||
style={{
|
||||
background: 'rgba(240, 185, 11, 0.15)',
|
||||
zIndex: -1
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{t('dashboardNav', language)}
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
// Landing page navigation when not logged in
|
||||
<a
|
||||
href='/competition'
|
||||
className='text-sm font-bold transition-all duration-300 relative focus:outline-2 focus:outline-yellow-500'
|
||||
style={{
|
||||
color: currentPage === 'competition' ? 'var(--brand-yellow)' : 'var(--brand-light-gray)',
|
||||
padding: '8px 16px',
|
||||
borderRadius: '8px',
|
||||
position: 'relative'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (currentPage !== 'competition') {
|
||||
e.currentTarget.style.color = 'var(--brand-yellow)';
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (currentPage !== 'competition') {
|
||||
e.currentTarget.style.color = 'var(--brand-light-gray)';
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* Background for selected state */}
|
||||
{currentPage === 'competition' && (
|
||||
<span
|
||||
className="absolute inset-0 rounded-lg"
|
||||
style={{
|
||||
background: 'rgba(240, 185, 11, 0.15)',
|
||||
zIndex: -1
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{t('realtimeNav', language)}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right Side - Original Navigation Items and Login */}
|
||||
<div className='flex items-center gap-6'>
|
||||
{/* Only show original navigation items on home page */}
|
||||
{isHomePage && [
|
||||
{ key: 'features', label: t('features', language) },
|
||||
{ key: 'howItWorks', label: t('howItWorks', language) },
|
||||
{ key: 'GitHub', label: 'GitHub' },
|
||||
{ key: 'community', label: t('community', language) }
|
||||
].map((item) => (
|
||||
<a
|
||||
key={item.key}
|
||||
href={
|
||||
item.key === 'GitHub'
|
||||
? 'https://github.com/tinkle-community/nofx'
|
||||
: item.key === 'community'
|
||||
? 'https://t.me/nofx_dev_community'
|
||||
: `#${item.key === 'features' ? 'features' : 'how-it-works'}`
|
||||
}
|
||||
target={item.key === 'GitHub' || item.key === 'community' ? '_blank' : undefined}
|
||||
rel={item.key === 'GitHub' || item.key === 'community' ? 'noopener noreferrer' : undefined}
|
||||
className='text-sm transition-colors relative group'
|
||||
style={{ color: 'var(--brand-light-gray)' }}
|
||||
>
|
||||
{item.label}
|
||||
<span
|
||||
className='absolute -bottom-1 left-0 w-0 h-0.5 group-hover:w-full transition-all duration-300'
|
||||
style={{ background: 'var(--brand-yellow)' }}
|
||||
/>
|
||||
</a>
|
||||
))}
|
||||
|
||||
{/* User Info and Actions */}
|
||||
{isLoggedIn && user ? (
|
||||
<div className='flex items-center gap-3'>
|
||||
{/* User Info with Dropdown */}
|
||||
<div className='relative' ref={userDropdownRef}>
|
||||
<button
|
||||
onClick={() => setUserDropdownOpen(!userDropdownOpen)}
|
||||
className='flex items-center gap-2 px-3 py-2 rounded transition-colors'
|
||||
style={{ background: 'var(--panel-bg)', border: '1px solid var(--panel-border)' }}
|
||||
onMouseEnter={(e) => e.currentTarget.style.background = 'rgba(255, 255, 255, 0.05)'}
|
||||
onMouseLeave={(e) => e.currentTarget.style.background = 'var(--panel-bg)'}
|
||||
>
|
||||
<div className='w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold' style={{ background: 'var(--brand-yellow)', color: 'var(--brand-black)' }}>
|
||||
{user.email[0].toUpperCase()}
|
||||
</div>
|
||||
<span className='text-sm' style={{ color: 'var(--brand-light-gray)' }}>{user.email}</span>
|
||||
<ChevronDown className='w-4 h-4' style={{ color: 'var(--brand-light-gray)' }} />
|
||||
</button>
|
||||
|
||||
{userDropdownOpen && (
|
||||
<div className='absolute right-0 top-full mt-2 w-48 rounded-lg shadow-lg overflow-hidden z-50' style={{ background: 'var(--brand-dark-gray)', border: '1px solid var(--panel-border)' }}>
|
||||
<div className='px-3 py-2 border-b' style={{ borderColor: 'var(--panel-border)' }}>
|
||||
<div className='text-xs' style={{ color: 'var(--text-secondary)' }}>{t('loggedInAs', language)}</div>
|
||||
<div className='text-sm font-medium' style={{ color: 'var(--brand-light-gray)' }}>{user.email}</div>
|
||||
</div>
|
||||
{!isAdminMode && onLogout && (
|
||||
<button
|
||||
onClick={() => {
|
||||
onLogout()
|
||||
setUserDropdownOpen(false)
|
||||
}}
|
||||
className='w-full px-3 py-2 text-sm font-semibold transition-colors hover:opacity-80 text-center'
|
||||
style={{ background: 'var(--binance-red-bg)', color: 'var(--binance-red)' }}
|
||||
>
|
||||
{t('exitLogin', language)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
/* Show login/register buttons when not logged in and not on login/register pages */
|
||||
currentPage !== 'login' && currentPage !== 'register' && (
|
||||
<div className='flex items-center gap-3'>
|
||||
<a
|
||||
href='/login'
|
||||
className='px-3 py-2 text-sm font-medium transition-colors rounded'
|
||||
style={{ color: 'var(--brand-light-gray)' }}
|
||||
>
|
||||
{t('signIn', language)}
|
||||
</a>
|
||||
<a
|
||||
href='/register'
|
||||
className='px-4 py-2 rounded font-semibold text-sm transition-colors hover:opacity-90'
|
||||
style={{ background: 'var(--brand-yellow)', color: 'var(--brand-black)' }}
|
||||
>
|
||||
{t('signUp', language)}
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
|
||||
{/* Language Toggle - Always at the rightmost */}
|
||||
<div className='relative' ref={dropdownRef}>
|
||||
<button
|
||||
onClick={() => setLanguageDropdownOpen(!languageDropdownOpen)}
|
||||
className='flex items-center gap-2 px-3 py-2 rounded transition-colors'
|
||||
style={{ color: 'var(--brand-light-gray)' }}
|
||||
onMouseEnter={(e) => e.currentTarget.style.background = 'rgba(255, 255, 255, 0.05)'}
|
||||
onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}
|
||||
>
|
||||
<span className='text-lg'>
|
||||
{language === 'zh' ? '🇨🇳' : '🇺🇸'}
|
||||
</span>
|
||||
<ChevronDown className='w-4 h-4' />
|
||||
</button>
|
||||
|
||||
{languageDropdownOpen && (
|
||||
<div className='absolute right-0 top-full mt-2 w-32 rounded-lg shadow-lg overflow-hidden z-50' style={{ background: 'var(--brand-dark-gray)', border: '1px solid var(--panel-border)' }}>
|
||||
<button
|
||||
onClick={() => {
|
||||
onLanguageChange?.('zh')
|
||||
setLanguageDropdownOpen(false)
|
||||
}}
|
||||
className={`w-full flex items-center gap-2 px-3 py-2 transition-colors ${
|
||||
language === 'zh' ? '' : 'hover:opacity-80'
|
||||
}`}
|
||||
style={{
|
||||
color: 'var(--brand-light-gray)',
|
||||
background: language === 'zh' ? 'rgba(240, 185, 11, 0.1)' : 'transparent'
|
||||
}}
|
||||
>
|
||||
<span className='text-base'>🇨🇳</span>
|
||||
<span className='text-sm'>中文</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
onLanguageChange?.('en')
|
||||
setLanguageDropdownOpen(false)
|
||||
}}
|
||||
className={`w-full flex items-center gap-2 px-3 py-2 transition-colors ${
|
||||
language === 'en' ? '' : 'hover:opacity-80'
|
||||
}`}
|
||||
style={{
|
||||
color: 'var(--brand-light-gray)',
|
||||
background: language === 'en' ? 'rgba(240, 185, 11, 0.1)' : 'transparent'
|
||||
}}
|
||||
>
|
||||
<span className='text-base'>🇺🇸</span>
|
||||
<span className='text-sm'>English</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu Button */}
|
||||
@@ -74,21 +381,232 @@ export default function HeaderBar({ onLoginClick }: { onLoginClick: () => void }
|
||||
style={{ background: 'var(--brand-dark-gray)', borderTop: '1px solid rgba(240, 185, 11, 0.1)' }}
|
||||
>
|
||||
<div className='px-4 py-4 space-y-3'>
|
||||
{['功能', '如何运作', 'GitHub', '社区'].map((item) => (
|
||||
<a key={item} href={`#${item}`} className='block text-sm py-2' style={{ color: 'var(--brand-light-gray)' }}>
|
||||
{item}
|
||||
{/* New Navigation Tabs */}
|
||||
{isLoggedIn ? (
|
||||
<button
|
||||
onClick={() => {
|
||||
console.log('移动端 实时 button clicked, onPageChange:', onPageChange);
|
||||
onPageChange?.('competition')
|
||||
setMobileMenuOpen(false)
|
||||
}}
|
||||
className='block text-sm font-bold transition-all duration-300 relative focus:outline-2 focus:outline-yellow-500'
|
||||
style={{
|
||||
color: currentPage === 'competition' ? 'var(--brand-yellow)' : 'var(--brand-light-gray)',
|
||||
padding: '12px 16px',
|
||||
borderRadius: '8px',
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
textAlign: 'left'
|
||||
}}
|
||||
>
|
||||
{/* Background for selected state */}
|
||||
{currentPage === 'competition' && (
|
||||
<span
|
||||
className="absolute inset-0 rounded-lg"
|
||||
style={{
|
||||
background: 'rgba(240, 185, 11, 0.15)',
|
||||
zIndex: -1
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{t('realtimeNav', language)}
|
||||
</button>
|
||||
) : (
|
||||
<a
|
||||
href='/competition'
|
||||
className='block text-sm font-bold transition-all duration-300 relative focus:outline-2 focus:outline-yellow-500'
|
||||
style={{
|
||||
color: currentPage === 'competition' ? 'var(--brand-yellow)' : 'var(--brand-light-gray)',
|
||||
padding: '12px 16px',
|
||||
borderRadius: '8px',
|
||||
position: 'relative'
|
||||
}}
|
||||
>
|
||||
{/* Background for selected state */}
|
||||
{currentPage === 'competition' && (
|
||||
<span
|
||||
className="absolute inset-0 rounded-lg"
|
||||
style={{
|
||||
background: 'rgba(240, 185, 11, 0.15)',
|
||||
zIndex: -1
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{t('realtimeNav', language)}
|
||||
</a>
|
||||
)}
|
||||
{/* Only show 配置 and 看板 when logged in */}
|
||||
{isLoggedIn && (
|
||||
<>
|
||||
<button
|
||||
onClick={() => {
|
||||
console.log('移动端 配置 button clicked, onPageChange:', onPageChange);
|
||||
onPageChange?.('traders')
|
||||
setMobileMenuOpen(false)
|
||||
}}
|
||||
className='block text-sm font-bold transition-all duration-300 relative focus:outline-2 focus:outline-yellow-500 hover:text-yellow-500'
|
||||
style={{
|
||||
color: currentPage === 'traders' ? 'var(--brand-yellow)' : 'var(--brand-light-gray)',
|
||||
padding: '12px 16px',
|
||||
borderRadius: '8px',
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
textAlign: 'left'
|
||||
}}
|
||||
>
|
||||
{/* Background for selected state */}
|
||||
{currentPage === 'traders' && (
|
||||
<span
|
||||
className="absolute inset-0 rounded-lg"
|
||||
style={{
|
||||
background: 'rgba(240, 185, 11, 0.15)',
|
||||
zIndex: -1
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{t('configNav', language)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
console.log('移动端 看板 button clicked, onPageChange:', onPageChange);
|
||||
onPageChange?.('trader')
|
||||
setMobileMenuOpen(false)
|
||||
}}
|
||||
className='block text-sm font-bold transition-all duration-300 relative focus:outline-2 focus:outline-yellow-500 hover:text-yellow-500'
|
||||
style={{
|
||||
color: currentPage === 'trader' ? 'var(--brand-yellow)' : 'var(--brand-light-gray)',
|
||||
padding: '12px 16px',
|
||||
borderRadius: '8px',
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
textAlign: 'left'
|
||||
}}
|
||||
>
|
||||
{/* Background for selected state */}
|
||||
{currentPage === 'trader' && (
|
||||
<span
|
||||
className="absolute inset-0 rounded-lg"
|
||||
style={{
|
||||
background: 'rgba(240, 185, 11, 0.15)',
|
||||
zIndex: -1
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{t('dashboardNav', language)}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Original Navigation Items - Only on home page */}
|
||||
{isHomePage && [
|
||||
{ key: 'features', label: t('features', language) },
|
||||
{ key: 'howItWorks', label: t('howItWorks', language) },
|
||||
{ key: 'GitHub', label: 'GitHub' },
|
||||
{ key: 'community', label: t('community', language) }
|
||||
].map((item) => (
|
||||
<a
|
||||
key={item.key}
|
||||
href={
|
||||
item.key === 'GitHub'
|
||||
? 'https://github.com/tinkle-community/nofx'
|
||||
: item.key === 'community'
|
||||
? 'https://t.me/nofx_dev_community'
|
||||
: `#${item.key === 'features' ? 'features' : 'how-it-works'}`
|
||||
}
|
||||
target={item.key === 'GitHub' || item.key === 'community' ? '_blank' : undefined}
|
||||
rel={item.key === 'GitHub' || item.key === 'community' ? 'noopener noreferrer' : undefined}
|
||||
className='block text-sm py-2'
|
||||
style={{ color: 'var(--brand-light-gray)' }}
|
||||
>
|
||||
{item.label}
|
||||
</a>
|
||||
))}
|
||||
<button
|
||||
onClick={() => {
|
||||
onLoginClick()
|
||||
setMobileMenuOpen(false)
|
||||
}}
|
||||
className='w-full px-4 py-2 rounded font-semibold text-sm mt-2'
|
||||
style={{ background: 'var(--brand-yellow)', color: 'var(--brand-black)' }}
|
||||
>
|
||||
登录 / 注册
|
||||
</button>
|
||||
|
||||
{/* Language Toggle */}
|
||||
<div className='py-2'>
|
||||
<div className='flex items-center gap-2 mb-2'>
|
||||
<span className='text-xs' style={{ color: 'var(--brand-light-gray)' }}>{t('language', language)}:</span>
|
||||
</div>
|
||||
<div className='space-y-1'>
|
||||
<button
|
||||
onClick={() => {
|
||||
onLanguageChange?.('zh')
|
||||
setMobileMenuOpen(false)
|
||||
}}
|
||||
className={`w-full flex items-center gap-3 px-3 py-2 rounded transition-colors ${
|
||||
language === 'zh' ? 'bg-yellow-500 text-black' : 'text-gray-400 hover:text-white'
|
||||
}`}
|
||||
>
|
||||
<span className='text-lg'>🇨🇳</span>
|
||||
<span className='text-sm'>中文</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
onLanguageChange?.('en')
|
||||
setMobileMenuOpen(false)
|
||||
}}
|
||||
className={`w-full flex items-center gap-3 px-3 py-2 rounded transition-colors ${
|
||||
language === 'en' ? 'bg-yellow-500 text-black' : 'text-gray-400 hover:text-white'
|
||||
}`}
|
||||
>
|
||||
<span className='text-lg'>🇺🇸</span>
|
||||
<span className='text-sm'>English</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* User info and logout for mobile when logged in */}
|
||||
{isLoggedIn && user && (
|
||||
<div className='mt-4 pt-4' style={{ borderTop: '1px solid var(--panel-border)' }}>
|
||||
<div className='flex items-center gap-2 px-3 py-2 mb-2 rounded' style={{ background: 'var(--panel-bg)' }}>
|
||||
<div className='w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold' style={{ background: 'var(--brand-yellow)', color: 'var(--brand-black)' }}>
|
||||
{user.email[0].toUpperCase()}
|
||||
</div>
|
||||
<div>
|
||||
<div className='text-xs' style={{ color: 'var(--text-secondary)' }}>{t('loggedInAs', language)}</div>
|
||||
<div className='text-sm' style={{ color: 'var(--brand-light-gray)' }}>{user.email}</div>
|
||||
</div>
|
||||
</div>
|
||||
{!isAdminMode && onLogout && (
|
||||
<button
|
||||
onClick={() => {
|
||||
onLogout()
|
||||
setMobileMenuOpen(false)
|
||||
}}
|
||||
className='w-full px-4 py-2 rounded text-sm font-semibold transition-colors text-center'
|
||||
style={{ background: 'var(--binance-red-bg)', color: 'var(--binance-red)' }}
|
||||
>
|
||||
{t('exitLogin', language)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Show login/register buttons when not logged in and not on login/register pages */}
|
||||
{!isLoggedIn && currentPage !== 'login' && currentPage !== 'register' && (
|
||||
<div className='space-y-2 mt-2'>
|
||||
<a
|
||||
href='/login'
|
||||
className='block w-full px-4 py-2 rounded text-sm font-medium text-center transition-colors'
|
||||
style={{ color: 'var(--brand-light-gray)', border: '1px solid var(--brand-light-gray)' }}
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
>
|
||||
{t('signIn', language)}
|
||||
</a>
|
||||
<a
|
||||
href='/register'
|
||||
className='block w-full px-4 py-2 rounded font-semibold text-sm text-center transition-colors'
|
||||
style={{ background: 'var(--brand-yellow)', color: 'var(--brand-black)' }}
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
>
|
||||
{t('signUp', language)}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</motion.div>
|
||||
</nav>
|
||||
|
||||
@@ -15,6 +15,11 @@ export const translations = {
|
||||
logout: 'Logout',
|
||||
switchTrader: 'Switch Trader:',
|
||||
view: 'View',
|
||||
|
||||
// Navigation
|
||||
realtimeNav: 'Live',
|
||||
configNav: 'Config',
|
||||
dashboardNav: 'Dashboard',
|
||||
|
||||
// Footer
|
||||
footerTitle: 'NOFX - AI Trading System',
|
||||
@@ -317,6 +322,96 @@ export const translations = {
|
||||
passwordRequired: 'Password is required',
|
||||
invalidEmail: 'Invalid email format',
|
||||
passwordTooShort: 'Password must be at least 6 characters',
|
||||
|
||||
// Landing Page
|
||||
features: 'Features',
|
||||
howItWorks: 'How it Works',
|
||||
community: 'Community',
|
||||
language: 'Language',
|
||||
loggedInAs: 'Logged in as',
|
||||
exitLogin: 'Sign Out',
|
||||
signIn: 'Sign In',
|
||||
signUp: 'Sign Up',
|
||||
|
||||
// Hero Section
|
||||
githubStarsInDays: '2.5K+ GitHub Stars in 3 days',
|
||||
heroTitle1: 'Read the Market.',
|
||||
heroTitle2: 'Write the Trade.',
|
||||
heroDescription: 'NOFX is the future standard for AI trading — an open, community-driven agentic trading OS. Supporting Binance, Aster DEX and other exchanges, self-hosted, multi-agent competition, let AI automatically make decisions, execute and optimize trades for you.',
|
||||
poweredBy: 'Powered by Aster DEX and Binance, strategically invested by Amber.ac.',
|
||||
|
||||
// Landing Page CTA
|
||||
readyToDefine: 'Ready to define the future of AI trading?',
|
||||
startWithCrypto: 'Starting with crypto markets, expanding to TradFi. NOFX is the infrastructure of AgentFi.',
|
||||
getStartedNow: 'Get Started Now',
|
||||
viewSourceCode: 'View Source Code',
|
||||
|
||||
// Features Section
|
||||
coreFeatures: 'Core Features',
|
||||
whyChooseNofx: 'Why Choose NOFX?',
|
||||
openCommunityDriven: 'Open source, transparent, community-driven AI trading OS',
|
||||
openSourceSelfHosted: '100% Open Source & Self-Hosted',
|
||||
openSourceDesc: 'Your framework, your rules. Non-black box, supports custom prompts and multi-models.',
|
||||
openSourceFeatures1: 'Fully open source code',
|
||||
openSourceFeatures2: 'Self-hosting deployment support',
|
||||
openSourceFeatures3: 'Custom AI prompts',
|
||||
openSourceFeatures4: 'Multi-model support (DeepSeek, Qwen)',
|
||||
multiAgentCompetition: 'Multi-Agent Intelligent Competition',
|
||||
multiAgentDesc: 'AI strategies battle at high speed in sandbox, survival of the fittest, achieving strategy evolution.',
|
||||
multiAgentFeatures1: 'Multiple AI agents running in parallel',
|
||||
multiAgentFeatures2: 'Automatic strategy optimization',
|
||||
multiAgentFeatures3: 'Sandbox security testing',
|
||||
multiAgentFeatures4: 'Cross-market strategy porting',
|
||||
secureReliableTrading: 'Secure and Reliable Trading',
|
||||
secureDesc: 'Enterprise-grade security, complete control over your funds and trading strategies.',
|
||||
secureFeatures1: 'Local private key management',
|
||||
secureFeatures2: 'Fine-grained API permission control',
|
||||
secureFeatures3: 'Real-time risk monitoring',
|
||||
secureFeatures4: 'Trading log auditing',
|
||||
|
||||
// About Section
|
||||
aboutNofx: 'About NOFX',
|
||||
whatIsNofx: 'What is NOFX?',
|
||||
nofxNotAnotherBot: "NOFX is not another trading bot, but the 'Linux' of AI trading —",
|
||||
nofxDescription1: 'a transparent, trustworthy open source OS that provides a unified',
|
||||
nofxDescription2: "'decision-risk-execution' layer, supporting all asset classes.",
|
||||
nofxDescription3: 'Starting with crypto markets (24/7, high volatility perfect testing ground), future expansion to stocks, futures, forex. Core: open architecture, AI',
|
||||
nofxDescription4: 'Darwinism (multi-agent self-competition, strategy evolution), CodeFi',
|
||||
nofxDescription5: 'flywheel (developers get point rewards for PR contributions).',
|
||||
youFullControl: 'You 100% Control',
|
||||
fullControlDesc: 'Complete control over AI prompts and funds',
|
||||
startupMessages1: 'Starting automated trading system...',
|
||||
startupMessages2: 'API server started on port 8080',
|
||||
startupMessages3: 'Web console http://localhost:3000',
|
||||
|
||||
// How It Works Section
|
||||
howToStart: 'How to Get Started with NOFX',
|
||||
fourSimpleSteps: 'Four simple steps to start your AI automated trading journey',
|
||||
step1Title: 'Clone GitHub Repository',
|
||||
step1Desc: 'git clone https://github.com/tinkle-community/nofx and switch to dev branch to test new features.',
|
||||
step2Title: 'Configure Environment',
|
||||
step2Desc: 'Frontend setup for exchange APIs (like Binance, Hyperliquid), AI models and custom prompts.',
|
||||
step3Title: 'Deploy & Run',
|
||||
step3Desc: 'One-click Docker deployment, start AI agents. Note: High-risk market, only test with money you can afford to lose.',
|
||||
step4Title: 'Optimize & Contribute',
|
||||
step4Desc: 'Monitor trading, submit PRs to improve framework. Join Telegram to share strategies.',
|
||||
importantRiskWarning: 'Important Risk Warning',
|
||||
riskWarningText: 'Dev branch is unstable, do not use funds you cannot afford to lose. NOFX is non-custodial, no official strategies. Trading involves risks, invest carefully.',
|
||||
|
||||
// Community Section (testimonials are kept as-is since they are quotes)
|
||||
|
||||
// Footer Section
|
||||
futureStandardAI: 'The future standard of AI trading',
|
||||
links: 'Links',
|
||||
resources: 'Resources',
|
||||
documentation: 'Documentation',
|
||||
supporters: 'Supporters',
|
||||
strategicInvestment: '(Strategic Investment)',
|
||||
|
||||
// Login Modal
|
||||
accessNofxPlatform: 'Access NOFX Platform',
|
||||
loginRegisterPrompt: 'Please login or register to access the full AI trading platform',
|
||||
registerNewAccount: 'Register New Account',
|
||||
},
|
||||
zh: {
|
||||
// Header
|
||||
@@ -332,6 +427,11 @@ export const translations = {
|
||||
logout: '退出',
|
||||
switchTrader: '切换交易员:',
|
||||
view: '查看',
|
||||
|
||||
// Navigation
|
||||
realtimeNav: '实时',
|
||||
configNav: '配置',
|
||||
dashboardNav: '看板',
|
||||
|
||||
// Footer
|
||||
footerTitle: 'NOFX - AI交易系统',
|
||||
@@ -634,6 +734,96 @@ export const translations = {
|
||||
passwordRequired: '请输入密码',
|
||||
invalidEmail: '邮箱格式不正确',
|
||||
passwordTooShort: '密码至少需要6个字符',
|
||||
|
||||
// Landing Page
|
||||
features: '功能',
|
||||
howItWorks: '如何运作',
|
||||
community: '社区',
|
||||
language: '语言',
|
||||
loggedInAs: '已登录为',
|
||||
exitLogin: '退出登录',
|
||||
signIn: '登录',
|
||||
signUp: '注册',
|
||||
|
||||
// Hero Section
|
||||
githubStarsInDays: '3 天内 2.5K+ GitHub Stars',
|
||||
heroTitle1: 'Read the Market.',
|
||||
heroTitle2: 'Write the Trade.',
|
||||
heroDescription: 'NOFX 是 AI 交易的未来标准——一个开放、社区驱动的代理式交易操作系统。支持 Binance、Aster DEX 等交易所,自托管、多代理竞争,让 AI 为你自动决策、执行和优化交易。',
|
||||
poweredBy: '由 Aster DEX 和 Binance 提供支持,Amber.ac 战略投资。',
|
||||
|
||||
// Landing Page CTA
|
||||
readyToDefine: '准备好定义 AI 交易的未来吗?',
|
||||
startWithCrypto: '从加密市场起步,扩展到 TradFi。NOFX 是 AgentFi 的基础架构。',
|
||||
getStartedNow: '立即开始',
|
||||
viewSourceCode: '查看源码',
|
||||
|
||||
// Features Section
|
||||
coreFeatures: '核心功能',
|
||||
whyChooseNofx: '为什么选择 NOFX?',
|
||||
openCommunityDriven: '开源、透明、社区驱动的 AI 交易操作系统',
|
||||
openSourceSelfHosted: '100% 开源与自托管',
|
||||
openSourceDesc: '你的框架,你的规则。非黑箱,支持自定义提示词和多模型。',
|
||||
openSourceFeatures1: '完全开源代码',
|
||||
openSourceFeatures2: '支持自托管部署',
|
||||
openSourceFeatures3: '自定义 AI 提示词',
|
||||
openSourceFeatures4: '多模型支持(DeepSeek、Qwen)',
|
||||
multiAgentCompetition: '多代理智能竞争',
|
||||
multiAgentDesc: 'AI 策略在沙盒中高速战斗,最优者生存,实现策略进化。',
|
||||
multiAgentFeatures1: '多 AI 代理并行运行',
|
||||
multiAgentFeatures2: '策略自动优化',
|
||||
multiAgentFeatures3: '沙盒安全测试',
|
||||
multiAgentFeatures4: '跨市场策略移植',
|
||||
secureReliableTrading: '安全可靠交易',
|
||||
secureDesc: '企业级安全保障,完全掌控你的资金和交易策略。',
|
||||
secureFeatures1: '本地私钥管理',
|
||||
secureFeatures2: 'API 权限精细控制',
|
||||
secureFeatures3: '实时风险监控',
|
||||
secureFeatures4: '交易日志审计',
|
||||
|
||||
// About Section
|
||||
aboutNofx: '关于 NOFX',
|
||||
whatIsNofx: '什么是 NOFX?',
|
||||
nofxNotAnotherBot: 'NOFX 不是另一个交易机器人,而是 AI 交易的 \'Linux\' ——',
|
||||
nofxDescription1: '一个透明、可信任的开源 OS,提供统一的 \'决策-风险-执行\'',
|
||||
nofxDescription2: '层,支持所有资产类别。',
|
||||
nofxDescription3: '从加密市场起步(24/7、高波动性完美测试场),未来扩展到股票、期货、外汇。核心:开放架构、AI',
|
||||
nofxDescription4: '达尔文主义(多代理自竞争、策略进化)、CodeFi 飞轮(开发者 PR',
|
||||
nofxDescription5: '贡献获积分奖励)。',
|
||||
youFullControl: '你 100% 掌控',
|
||||
fullControlDesc: '完全掌控 AI 提示词和资金',
|
||||
startupMessages1: ' 启动自动交易系统...',
|
||||
startupMessages2: ' API服务器启动在端口 8080',
|
||||
startupMessages3: ' Web 控制台 http://localhost:3000',
|
||||
|
||||
// How It Works Section
|
||||
howToStart: '如何开始使用 NOFX',
|
||||
fourSimpleSteps: '四个简单步骤,开启 AI 自动交易之旅',
|
||||
step1Title: '拉取 GitHub 仓库',
|
||||
step1Desc: 'git clone https://github.com/tinkle-community/nofx 并切换到 dev 分支测试新功能。',
|
||||
step2Title: '配置环境',
|
||||
step2Desc: '前端设置交易所 API(如 Binance、Hyperliquid)、AI 模型和自定义提示词。',
|
||||
step3Title: '部署与运行',
|
||||
step3Desc: '一键 Docker 部署,启动 AI 代理。注意:高风险市场,仅用闲钱测试。',
|
||||
step4Title: '优化与贡献',
|
||||
step4Desc: '监控交易,提交 PR 改进框架。加入 Telegram 分享策略。',
|
||||
importantRiskWarning: '重要风险提示',
|
||||
riskWarningText: 'dev 分支不稳定,勿用无法承受损失的资金。NOFX 非托管,无官方策略。交易有风险,投资需谨慎。',
|
||||
|
||||
// Community Section (testimonials are kept as-is since they are quotes)
|
||||
|
||||
// Footer Section
|
||||
futureStandardAI: 'AI 交易的未来标准',
|
||||
links: '链接',
|
||||
resources: '资源',
|
||||
documentation: '文档',
|
||||
supporters: '支持方',
|
||||
strategicInvestment: '(战略投资)',
|
||||
|
||||
// Login Modal
|
||||
accessNofxPlatform: '访问 NOFX 平台',
|
||||
loginRegisterPrompt: '请选择登录或注册以访问完整的 AI 交易平台',
|
||||
registerNewAccount: '注册新账号',
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user