Skip to content

Commit e080850

Browse files
committed
Function: Add translation helper page
1 parent c5fedc3 commit e080850

File tree

10 files changed

+349
-9
lines changed

10 files changed

+349
-9
lines changed

src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useEffect, useState } from 'react';
22
import { ChatPage } from './components/pages/ChatPage';
33
import { ImageGenerationPage } from './components/pages/ImageGenerationPage';
4+
import { TranslationPage } from './components/pages/TranslationPage';
45
import MainLayout from './components/layout/MainLayout';
56
import DatabaseInitializer from './components/core/DatabaseInitializer';
67

@@ -34,6 +35,7 @@ function App() {
3435
<MainLayout activePage={activePage} onChangePage={handlePageChange} showSettings={showSettings} setShowSettings={setShowSettings}>
3536
{activePage === 'chat' && <ChatPage />}
3637
{activePage === 'image' && <ImageGenerationPage />}
38+
{activePage === 'translation' && <TranslationPage />}
3739
</MainLayout>
3840
</DatabaseInitializer>
3941
);

src/components/layout/MainLayout.tsx

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,13 @@ const MainLayout: React.FC<MainLayoutProps> = ({
2222

2323
// Handle settings dialog
2424
const handleOpenSettingsDialog = () => {
25-
if(activePage === 'settings' && activePage !== 'settings'){
26-
// Save settings
27-
SettingsService.getInstance().saveSettings();
28-
}
29-
3025
setShowSettings(true);
31-
onChangePage('settings');
3226
};
3327

3428
// Handle page changes
3529
const handlePageChange = (page: string) => {
3630
if (page === 'settings') {
3731
setShowSettings(true);
38-
onChangePage('settings');
3932
} else {
4033
setShowSettings(false);
4134
onChangePage(page);

src/components/layout/Sidebar.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { MessageSquare, Settings, Image } from 'lucide-react';
2+
import { MessageSquare, Settings, Image, Languages } from 'lucide-react';
33

44
interface SidebarProps {
55
activePage: string;
@@ -25,6 +25,9 @@ export const Sidebar: React.FC<SidebarProps> = ({
2525
else if(activePage === 'image'){
2626
return 'image';
2727
}
28+
else if(activePage === 'translation'){
29+
return 'translation';
30+
}
2831

2932
return '';
3033
}
@@ -58,7 +61,17 @@ export const Sidebar: React.FC<SidebarProps> = ({
5861
<Image size={22} />
5962
</button>
6063

61-
{/* Add more navigation buttons here as needed */}
64+
<button
65+
className={`w-12 h-12 rounded-lg flex items-center justify-center transition-all duration-200 ${
66+
getActivePage() === 'translation'
67+
? 'navigation-item-selected navigation-item-text'
68+
: 'navigation-item navigation-item-text'
69+
}`}
70+
onClick={() => onChangePage('translation')}
71+
aria-label="Translation"
72+
>
73+
<Languages size={22} />
74+
</button>
6275
</div>
6376

6477
{/* Settings button at bottom */}
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import React, { useState, useEffect } from 'react';
2+
import { SettingsService, SETTINGS_CHANGE_EVENT } from '../../services/settings-service';
3+
import { Orbit, Languages, ArrowRight } from 'lucide-react';
4+
import { useTranslation } from 'react-i18next';
5+
6+
export const TranslationPage: React.FC = () => {
7+
const { t } = useTranslation();
8+
const [sourceText, setSourceText] = useState('');
9+
const [translatedText, setTranslatedText] = useState('');
10+
const [isTranslating, setIsTranslating] = useState(false);
11+
const [sourceLanguage, setSourceLanguage] = useState('auto');
12+
const [targetLanguage, setTargetLanguage] = useState('en');
13+
const [error, setError] = useState<Error | null>(null);
14+
const [isApiKeyMissing, setIsApiKeyMissing] = useState(true);
15+
16+
// Check if API key is available
17+
useEffect(() => {
18+
setIsApiKeyMissing(!SettingsService.getInstance().getApiKey());
19+
20+
const handleSettingsChange = () => {
21+
setIsApiKeyMissing(!SettingsService.getInstance().getApiKey());
22+
};
23+
24+
window.addEventListener(SETTINGS_CHANGE_EVENT, handleSettingsChange);
25+
26+
return () => {
27+
window.removeEventListener(SETTINGS_CHANGE_EVENT, handleSettingsChange);
28+
};
29+
}, []);
30+
31+
// Handle translation
32+
const handleTranslate = async () => {
33+
if (!sourceText.trim()) return;
34+
35+
setIsTranslating(true);
36+
setError(null);
37+
38+
try {
39+
// This would be replaced with actual translation logic
40+
// using a language model API
41+
setTimeout(() => {
42+
setTranslatedText(sourceText);
43+
setIsTranslating(false);
44+
}, 1000);
45+
} catch (err) {
46+
setError(err as Error);
47+
setIsTranslating(false);
48+
}
49+
};
50+
51+
// Languages for dropdown
52+
const languages = [
53+
{ code: 'auto', name: t('translation.autoDetect') },
54+
{ code: 'en', name: t('translation.english') },
55+
{ code: 'es', name: t('translation.spanish') },
56+
{ code: 'fr', name: t('translation.french') },
57+
{ code: 'de', name: t('translation.german') },
58+
{ code: 'it', name: t('translation.italian') },
59+
{ code: 'pt', name: t('translation.portuguese') },
60+
{ code: 'ru', name: t('translation.russian') },
61+
{ code: 'zh', name: t('translation.chinese') },
62+
{ code: 'ja', name: t('translation.japanese') },
63+
{ code: 'ko', name: t('translation.korean') },
64+
];
65+
66+
return (
67+
<div className="flex flex-col w-full h-full bg-white">
68+
{isApiKeyMissing && (
69+
<div className="p-2 text-sm text-center text-yellow-800 bg-yellow-100">
70+
{t('translation.apiKeyMissing')}
71+
</div>
72+
)}
73+
74+
<div className="flex flex-row h-full">
75+
{/* Left side - Source */}
76+
<div className="flex-1 p-6 overflow-hidden frame-right-border">
77+
<div className="flex flex-col h-full">
78+
<div className="flex items-center justify-between gap-4 mb-4">
79+
80+
{/* Language selector */}
81+
<div className="relative flex items-center gap-4">
82+
<h1 className="text-2xl font-semibold">
83+
{t('translation.title')}
84+
</h1>
85+
86+
<select
87+
value={sourceLanguage}
88+
onChange={(e) => setSourceLanguage(e.target.value)}
89+
className="px-4 py-2 input-box"
90+
>
91+
{languages.map((lang) => (
92+
<option key={`source-${lang.code}`} value={lang.code}>
93+
{lang.name}
94+
</option>
95+
))}
96+
</select>
97+
</div>
98+
99+
<button
100+
onClick={handleTranslate}
101+
disabled={isTranslating || !sourceText.trim() || isApiKeyMissing}
102+
className="px-8 py-2.5 text-white confirm-btn flex items-center"
103+
>
104+
{isTranslating ? (
105+
<>
106+
<Orbit size={18} className="mr-2 animate-spin" />
107+
{t('translation.translating')}
108+
</>
109+
) : (
110+
<>
111+
<Languages size={18} className="mr-2" />
112+
{t('translation.translateButton')}
113+
</>
114+
)}
115+
</button>
116+
</div>
117+
118+
{/* Source text input */}
119+
<textarea
120+
value={sourceText}
121+
onChange={(e) => setSourceText(e.target.value)}
122+
placeholder={t('translation.inputPlaceholder')}
123+
className="flex-1 w-full p-3 mb-4 form-textarea-border input-box"
124+
style={{ minHeight: '200px', resize: 'none' }}
125+
/>
126+
127+
{/* Translation button */}
128+
{/* <div className="flex justify-center">
129+
<button
130+
onClick={handleTranslate}
131+
disabled={isTranslating || !sourceText.trim() || isApiKeyMissing}
132+
className="px-8 py-2.5 text-white confirm-btn flex items-center"
133+
>
134+
{isTranslating ? (
135+
<>
136+
<Orbit size={18} className="mr-2 animate-spin" />
137+
{t('translation.translating')}
138+
</>
139+
) : (
140+
<>
141+
<Languages size={18} className="mr-2" />
142+
{t('translation.translateButton')}
143+
</>
144+
)}
145+
</button>
146+
</div> */}
147+
</div>
148+
</div>
149+
150+
{/* Right side - Translation result */}
151+
<div className="flex-1 p-6 overflow-hidden">
152+
<div className="flex flex-col h-full">
153+
<div className="flex items-center gap-4 mb-4">
154+
<h2 className="flex items-center text-xl font-medium">
155+
<ArrowRight size={24} />
156+
</h2>
157+
158+
{/* Target language selector */}
159+
<div className="relative">
160+
<select
161+
value={targetLanguage}
162+
onChange={(e) => setTargetLanguage(e.target.value)}
163+
className="px-4 py-2 input-box"
164+
>
165+
{languages.filter(lang => lang.code !== 'auto').map((lang) => (
166+
<option key={`target-${lang.code}`} value={lang.code}>
167+
{lang.name}
168+
</option>
169+
))}
170+
</select>
171+
</div>
172+
</div>
173+
174+
{/* Error message */}
175+
{error && (
176+
<div className="p-3 mb-4 text-red-700 bg-red-100 rounded-lg">
177+
{t('common.error')}: {error.message}
178+
</div>
179+
)}
180+
181+
{/* Translation result */}
182+
<div className="flex-1 p-3 overflow-auto form-textarea-border major-area-bg-color">
183+
{isTranslating ? (
184+
<div className="flex items-center justify-center h-full">
185+
<div className="w-8 h-8 border-4 rounded-full border-primary-300 border-t-primary-600 animate-spin"></div>
186+
</div>
187+
) : (
188+
<div className="h-full">
189+
{translatedText ? (
190+
<p className="text-primary-800">{translatedText}</p>
191+
) : (
192+
<div className="flex items-center justify-center h-full">
193+
<p className="text-surface-400">{t('translation.resultPlaceholder')}</p>
194+
</div>
195+
)}
196+
</div>
197+
)}
198+
</div>
199+
</div>
200+
</div>
201+
</div>
202+
</div>
203+
);
204+
};
205+
206+
export default TranslationPage;

src/locales/en/translation.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,27 @@
3232
"stopResponse": "Stop Response",
3333
"pressShiftEnterToChangeLines": "Press Shift+Enter to change lines"
3434
},
35+
"translation": {
36+
"title": "Translation",
37+
"sourceLanguage": "Source Language",
38+
"result": "Translation Result",
39+
"translateButton": "Translate",
40+
"translating": "Translating...",
41+
"inputPlaceholder": "Enter text to translate...",
42+
"resultPlaceholder": "Translation will appear here",
43+
"apiKeyMissing": "Please set your API key for the selected provider in the settings.",
44+
"autoDetect": "Auto-detect",
45+
"english": "English",
46+
"spanish": "Spanish",
47+
"french": "French",
48+
"german": "German",
49+
"italian": "Italian",
50+
"portuguese": "Portuguese",
51+
"russian": "Russian",
52+
"chinese": "Chinese",
53+
"japanese": "Japanese",
54+
"korean": "Korean"
55+
},
3556
"selectModel": {
3657
"selectModel_title": "Select Model",
3758
"selectModel_close": "Close",

src/locales/es/translation.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,27 @@
3232
"stopResponse": "Detener Respuesta",
3333
"pressShiftEnterToChangeLines": "Presiona Shift+Enter para cambiar de línea"
3434
},
35+
"translation": {
36+
"title": "Traducción",
37+
"sourceLanguage": "Idioma de Origen",
38+
"result": "Resultado de la Traducción",
39+
"translateButton": "Traducir",
40+
"translating": "Traduciendo...",
41+
"inputPlaceholder": "Introduce el texto a traducir...",
42+
"resultPlaceholder": "La traducción aparecerá aquí",
43+
"apiKeyMissing": "Por favor, configura tu clave API para el proveedor seleccionado en la configuración.",
44+
"autoDetect": "Detección automática",
45+
"english": "Inglés",
46+
"spanish": "Español",
47+
"french": "Francés",
48+
"german": "Alemán",
49+
"italian": "Italiano",
50+
"portuguese": "Portugués",
51+
"russian": "Ruso",
52+
"chinese": "Chino",
53+
"japanese": "Japonés",
54+
"korean": "Coreano"
55+
},
3556
"selectModel": {
3657
"selectModel_title": "Seleccionar Modelo",
3758
"selectModel_close": "Cerrar",

src/locales/ja/translation.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,27 @@
3232
"stopResponse": "応答を停止",
3333
"pressShiftEnterToChangeLines": "Shift+Enterを押して行を変更"
3434
},
35+
"translation": {
36+
"title": "翻訳",
37+
"sourceLanguage": "ソース言語",
38+
"result": "翻訳結果",
39+
"translateButton": "翻訳",
40+
"translating": "翻訳中...",
41+
"inputPlaceholder": "翻訳するテキストを入力...",
42+
"resultPlaceholder": "翻訳結果がここに表示されます",
43+
"apiKeyMissing": "選択したプロバイダーのAPIキーを設定で設定してください。",
44+
"autoDetect": "自動検出",
45+
"english": "英語",
46+
"spanish": "スペイン語",
47+
"french": "フランス語",
48+
"german": "ドイツ語",
49+
"italian": "イタリア語",
50+
"portuguese": "ポルトガル語",
51+
"russian": "ロシア語",
52+
"chinese": "中国語",
53+
"japanese": "日本語",
54+
"korean": "韓国語"
55+
},
3556
"selectModel": {
3657
"selectModel_title": "モデルを選択",
3758
"selectModel_close": "閉じる",

src/locales/ko/translation.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,27 @@
3232
"stopResponse": "응답 중지",
3333
"pressShiftEnterToChangeLines": "줄을 바꾸려면 Shift+Enter를 누르세요"
3434
},
35+
"translation": {
36+
"title": "번역",
37+
"sourceLanguage": "원본 언어",
38+
"result": "번역 결과",
39+
"translateButton": "번역",
40+
"translating": "번역 중...",
41+
"inputPlaceholder": "번역할 텍스트를 입력하세요...",
42+
"resultPlaceholder": "번역 결과가 여기에 표시됩니다",
43+
"apiKeyMissing": "선택한 제공자의 API 키를 설정에서 설정하세요.",
44+
"autoDetect": "자동 감지",
45+
"english": "영어",
46+
"spanish": "스페인어",
47+
"french": "프랑스어",
48+
"german": "독일어",
49+
"italian": "이탈리아어",
50+
"portuguese": "포르투갈어",
51+
"russian": "러시아어",
52+
"chinese": "중국어",
53+
"japanese": "일본어",
54+
"korean": "한국어"
55+
},
3556
"selectModel": {
3657
"selectModel_title": "모델 선택",
3758
"selectModel_close": "닫기",

0 commit comments

Comments
 (0)