Add i18n system with 4-language support (en/zh/ja/ko)
- Add I18nContext provider with useI18n hook and localStorage persistence - Add translations.ts with complete strings for English, Chinese, Japanese, Korean - Wrap App in I18nProvider, replace hardcoded toast messages with t() calls - Add language selector to SettingsModal with Globe icon - Add localization credits in About section - Default language: English - Add I18N_USAGE.md documentation
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { X, User as UserIcon, Palette, Info, Edit3, ExternalLink, Github } from 'lucide-react';
|
||||
import { X, User as UserIcon, Palette, Info, Edit3, ExternalLink, Globe, ChevronDown, Github } from 'lucide-react';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { useI18n } from '../context/I18nContext';
|
||||
import { EditProfileModal } from './EditProfileModal';
|
||||
|
||||
interface SettingsModalProps {
|
||||
@@ -13,6 +14,7 @@ interface SettingsModalProps {
|
||||
|
||||
export const SettingsModal: React.FC<SettingsModalProps> = ({ isOpen, onClose, theme, onToggleTheme, onNavigateToProfile }) => {
|
||||
const { user } = useAuth();
|
||||
const { t, language, setLanguage } = useI18n();
|
||||
const [isEditProfileOpen, setIsEditProfileOpen] = useState(false);
|
||||
|
||||
if (!isOpen || !user) {
|
||||
@@ -29,14 +31,14 @@ export const SettingsModal: React.FC<SettingsModalProps> = ({ isOpen, onClose, t
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4" onClick={onClose}>
|
||||
<div className="fixed inset-0 bg-black/50 z-[60] flex items-center justify-center p-4" onClick={onClose}>
|
||||
<div
|
||||
className="bg-white dark:bg-zinc-900 rounded-2xl shadow-2xl max-w-2xl w-full max-h-[90vh] overflow-y-auto"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-6 border-b border-zinc-200 dark:border-white/5">
|
||||
<h2 className="text-2xl font-bold text-zinc-900 dark:text-white">Settings</h2>
|
||||
<h2 className="text-2xl font-bold text-zinc-900 dark:text-white">{t('settings')}</h2>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="p-2 hover:bg-zinc-100 dark:hover:bg-white/5 rounded-full transition-colors"
|
||||
@@ -59,7 +61,7 @@ export const SettingsModal: React.FC<SettingsModalProps> = ({ isOpen, onClose, t
|
||||
<div className="flex-1">
|
||||
<h3 className="text-xl font-bold text-zinc-900 dark:text-white">@{user.username}</h3>
|
||||
<p className="text-xs text-zinc-400 dark:text-zinc-500 mt-1">
|
||||
Member since {new Date(user.createdAt).toLocaleDateString('en-US', { month: 'long', year: 'numeric' })}
|
||||
{t('memberSince')} {new Date(user.createdAt).toLocaleDateString(language === 'zh' ? 'zh-CN' : 'en-US', { month: 'long', year: 'numeric' })}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
@@ -71,7 +73,7 @@ export const SettingsModal: React.FC<SettingsModalProps> = ({ isOpen, onClose, t
|
||||
className="flex items-center gap-2 px-4 py-2 bg-indigo-600 text-white rounded-lg text-sm font-medium hover:bg-indigo-700 transition-colors"
|
||||
>
|
||||
<Edit3 size={16} />
|
||||
Edit Profile
|
||||
{t('editProfile')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
@@ -81,7 +83,7 @@ export const SettingsModal: React.FC<SettingsModalProps> = ({ isOpen, onClose, t
|
||||
className="flex items-center gap-2 px-4 py-2 bg-zinc-200 dark:bg-zinc-700 text-zinc-900 dark:text-white rounded-lg text-sm font-medium hover:bg-zinc-300 dark:hover:bg-zinc-600 transition-colors"
|
||||
>
|
||||
<ExternalLink size={16} />
|
||||
View Profile
|
||||
{t('viewProfile')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -91,21 +93,47 @@ export const SettingsModal: React.FC<SettingsModalProps> = ({ isOpen, onClose, t
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2 text-zinc-900 dark:text-white">
|
||||
<UserIcon size={20} />
|
||||
<h3 className="font-semibold">Account</h3>
|
||||
<h3 className="font-semibold">{t('account')}</h3>
|
||||
</div>
|
||||
<div className="pl-7 space-y-3">
|
||||
<div>
|
||||
<label className="text-sm text-zinc-500 dark:text-zinc-400">Username</label>
|
||||
<label className="text-sm text-zinc-500 dark:text-zinc-400">{t('username')}</label>
|
||||
<p className="text-zinc-900 dark:text-white font-medium">@{user.username}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Language Section */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2 text-zinc-900 dark:text-white">
|
||||
<Globe size={20} />
|
||||
<h3 className="font-semibold">{t('language')}</h3>
|
||||
</div>
|
||||
<div className="pl-7 space-y-3">
|
||||
<div className="relative">
|
||||
<select
|
||||
value={language}
|
||||
onChange={(e) => setLanguage(e.target.value as 'en' | 'zh' | 'ja' | 'ko')}
|
||||
className="w-full appearance-none py-3 px-4 pr-10 rounded-lg border-2 border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-800 text-zinc-900 dark:text-white font-medium transition-colors hover:border-zinc-400 dark:hover:border-zinc-600 focus:outline-none focus:border-indigo-500 dark:focus:border-indigo-500 cursor-pointer"
|
||||
>
|
||||
<option value="en">{t('english')}</option>
|
||||
<option value="zh">{t('chinese')}</option>
|
||||
<option value="ja">{t('japaneseLanguage')}</option>
|
||||
<option value="ko">{t('koreanLanguage')}</option>
|
||||
</select>
|
||||
<ChevronDown
|
||||
size={20}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-zinc-500 pointer-events-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Theme Section */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2 text-zinc-900 dark:text-white">
|
||||
<Palette size={20} />
|
||||
<h3 className="font-semibold">Appearance</h3>
|
||||
<h3 className="font-semibold">{t('appearance')}</h3>
|
||||
</div>
|
||||
<div className="pl-7 space-y-3">
|
||||
<div className="flex gap-3">
|
||||
@@ -116,7 +144,7 @@ export const SettingsModal: React.FC<SettingsModalProps> = ({ isOpen, onClose, t
|
||||
: 'border-zinc-300 dark:border-zinc-700 hover:border-zinc-400 dark:hover:border-zinc-600'
|
||||
}`}
|
||||
>
|
||||
Light
|
||||
{t('light')}
|
||||
</button>
|
||||
<button
|
||||
onClick={theme === 'light' ? onToggleTheme : undefined}
|
||||
@@ -125,7 +153,7 @@ export const SettingsModal: React.FC<SettingsModalProps> = ({ isOpen, onClose, t
|
||||
: 'border-zinc-300 dark:border-zinc-700 hover:border-zinc-400 dark:hover:border-zinc-600'
|
||||
}`}
|
||||
>
|
||||
Dark
|
||||
{t('dark')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -135,41 +163,70 @@ export const SettingsModal: React.FC<SettingsModalProps> = ({ isOpen, onClose, t
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2 text-zinc-900 dark:text-white">
|
||||
<Info size={20} />
|
||||
<h3 className="font-semibold">About</h3>
|
||||
<h3 className="font-semibold">{t('about')}</h3>
|
||||
</div>
|
||||
<div className="pl-7 space-y-3 text-sm text-zinc-600 dark:text-zinc-400">
|
||||
<p>Version 1.0.0</p>
|
||||
<p>ACE-Step UI - Local AI Music Generator</p>
|
||||
<p>{t('version')} 2.0.0</p>
|
||||
<p>ACE-Step UI - {t('localAIMusicGenerator')}</p>
|
||||
<p className="text-xs text-zinc-400 dark:text-zinc-500 mt-2">
|
||||
Powered by ACE-Step 1.5. Open source and free to use.
|
||||
{t('poweredBy')}
|
||||
</p>
|
||||
<div className="pt-3 border-t border-zinc-200 dark:border-zinc-700/50 mt-4">
|
||||
<p className="text-zinc-900 dark:text-white font-medium mb-3">Created by Ambsd</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<a
|
||||
href="https://x.com/AmbsdOP"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-2 px-4 py-2 bg-black dark:bg-white text-white dark:text-black rounded-lg text-sm font-medium hover:bg-zinc-800 dark:hover:bg-zinc-200 transition-colors"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
|
||||
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
|
||||
</svg>
|
||||
Follow @AmbsdOP
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/fspecii/ace-step-ui"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-2 px-4 py-2 bg-zinc-800 dark:bg-zinc-700 text-white rounded-lg text-sm font-medium hover:bg-zinc-700 dark:hover:bg-zinc-600 transition-colors"
|
||||
>
|
||||
<Github size={16} />
|
||||
GitHub Repo
|
||||
</a>
|
||||
<div className="pt-3 border-t border-zinc-200 dark:border-zinc-700/50 mt-4 space-y-4">
|
||||
<div>
|
||||
<p className="text-zinc-900 dark:text-white font-medium mb-2">{t('createdBy')}</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<a
|
||||
href="https://x.com/AmbsdOP"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-2 px-4 py-2 bg-black dark:bg-white text-white dark:text-black rounded-lg text-sm font-medium hover:bg-zinc-800 dark:hover:bg-zinc-200 transition-colors"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
|
||||
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
|
||||
</svg>
|
||||
{t('follow')} @AmbsdOP
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/fspecii/ace-step-ui"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-2 px-4 py-2 bg-zinc-800 dark:bg-zinc-700 text-white rounded-lg text-sm font-medium hover:bg-zinc-700 dark:hover:bg-zinc-600 transition-colors"
|
||||
>
|
||||
<Github size={16} />
|
||||
GitHub Repo
|
||||
</a>
|
||||
</div>
|
||||
<p className="text-xs text-zinc-400 dark:text-zinc-500 mt-2">
|
||||
Report issues or request features on GitHub
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-zinc-900 dark:text-white font-medium mb-2">{t('localizedBy')}</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<a
|
||||
href="https://x.com/bdsqlsz"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-2 px-4 py-2 bg-black dark:bg-white text-white dark:text-black rounded-lg text-sm font-medium hover:bg-zinc-800 dark:hover:bg-zinc-200 transition-colors"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
|
||||
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
|
||||
</svg>
|
||||
{t('follow')} @bdsqlsz
|
||||
</a>
|
||||
<a
|
||||
href="https://space.bilibili.com/219296"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-2 px-4 py-2 bg-[#00A1D6] text-white rounded-lg text-sm font-medium hover:bg-[#0090C0] transition-colors"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
|
||||
<path d="M17.813 4.653h.854c1.51.054 2.769.578 3.773 1.574 1.004.995 1.524 2.249 1.56 3.76v7.36c-.036 1.51-.556 2.769-1.56 3.773s-2.262 1.524-3.773 1.56H5.333c-1.51-.036-2.769-.556-3.773-1.56S.036 18.858 0 17.347v-7.36c.036-1.511.556-2.765 1.56-3.76 1.004-.996 2.262-1.52 3.773-1.574h.774l-1.174-1.12a1.234 1.234 0 0 1-.373-.906c0-.356.124-.658.373-.907l.027-.027c.267-.249.573-.373.92-.373.347 0 .653.124.92.373L9.653 4.44c.071.071.134.142.187.213h4.267a.836.836 0 0 1 .16-.213l2.853-2.747c.267-.249.573-.373.92-.373.347 0 .662.151.929.4.267.249.391.551.391.907 0 .355-.124.657-.373.906zM5.333 7.24c-.746.018-1.373.276-1.88.773-.506.498-.769 1.13-.786 1.894v7.52c.017.764.28 1.395.786 1.893.507.498 1.134.756 1.88.773h13.334c.746-.017 1.373-.275 1.88-.773.506-.498.769-1.129.786-1.893v-7.52c-.017-.765-.28-1.396-.786-1.894-.507-.497-1.134-.755-1.88-.773zM8 11.107c.373 0 .684.124.933.373.25.249.383.569.4.96v1.173c-.017.391-.15.711-.4.96-.249.25-.56.374-.933.374s-.684-.125-.933-.374c-.25-.249-.383-.569-.4-.96V12.44c0-.373.129-.689.386-.947.258-.257.574-.386.947-.386zm8 0c.373 0 .684.124.933.373.25.249.383.569.4.96v1.173c-.017.391-.15.711-.4.96-.249.25-.56.374-.933.374s-.684-.125-.933-.374c-.25-.249-.383-.569-.4-.96V12.44c.017-.391.15-.711.4-.96.249-.249.56-.373.933-.373Z"/>
|
||||
</svg>
|
||||
{t('follow')} 青龙圣者
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-zinc-400 dark:text-zinc-500 mt-3">
|
||||
Report issues or request features on GitHub
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -181,7 +238,7 @@ export const SettingsModal: React.FC<SettingsModalProps> = ({ isOpen, onClose, t
|
||||
onClick={onClose}
|
||||
className="px-6 py-2 bg-zinc-900 dark:bg-white text-white dark:text-black font-semibold rounded-lg hover:bg-zinc-800 dark:hover:bg-zinc-200 transition-colors"
|
||||
>
|
||||
Done
|
||||
{t('done')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user