diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6a9b1d3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +node_modules/ +dist/ +release/ +.DS_Store +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.idea/ +.vscode/ diff --git a/App.tsx b/App.tsx index e273224..b94fdb6 100644 --- a/App.tsx +++ b/App.tsx @@ -1,11 +1,25 @@ import React, { useState, useCallback, useMemo } from 'react'; import { DumpOptions, Preset } from './types'; -import { PRESETS } from './constants'; import { createCodeDump } from './services/zipProcessor'; import { OptionsPanel } from './components/OptionsPanel'; import { FileUpload } from './components/FileUpload'; import { OutputDisplay } from './components/OutputDisplay'; +import { useTranslation } from './i18n'; +import { LanguageSelector } from './components/LanguageSelector'; +import { Button } from './components/ui/Button'; + +const DownloadIcon = () => ( + + + +); + +const CopyIcon = () => ( + + + +); const defaultOptions: DumpOptions = { include: ['**/*'], @@ -17,19 +31,23 @@ const defaultOptions: DumpOptions = { strictText: false, }; -const LoadingIndicator: React.FC<{ progress: number }> = ({ progress }) => ( -
- - - - -

Processing ZIP file...

-
-
+const LoadingIndicator: React.FC<{ progress: number }> = ({ progress }) => { + const { t } = useTranslation(); + + return ( +
+ + + + +

{t('loading.processing')}

+
+
+
+

{t('loading.percentComplete', { percent: progress })}

-

{progress}% complete

-
-); + ); +}; function App() { @@ -38,6 +56,7 @@ function App() { const [output, setOutput] = useState(null); const [isLoading, setIsLoading] = useState(false); const [progress, setProgress] = useState(0); + const { t } = useTranslation(); const handlePresetChange = useCallback((preset: Preset) => { const newOptions: DumpOptions = { @@ -57,13 +76,18 @@ function App() { if (file.type === 'application/zip' || file.type === 'application/x-zip-compressed') { setZipFile(file); } else { - alert('Please select a valid ZIP file.'); + alert(t('app.invalidZip')); } - }, []); + }, [t]); + + const downloadName = useMemo(() => { + const normalized = zipFile?.name?.replace(/\.zip$/i, '') || 'codedump'; + return `${normalized}.md`; + }, [zipFile]); const handleGenerate = async () => { if (!zipFile) { - alert('Please select a ZIP file first.'); + alert(t('app.noZip')); return; } setIsLoading(true); @@ -75,24 +99,47 @@ function App() { setOutput(markdownContent); } catch (error) { console.error('Failed to process ZIP file:', error); - alert(`An error occurred: ${error instanceof Error ? error.message : 'Unknown error'}`); + const message = error instanceof Error ? error.message : 'Unknown error'; + alert(t('app.error', { message })); setOutput(null); } finally { setIsLoading(false); } }; - const handleReset = () => { + const handleReset = useCallback(() => { setZipFile(null); setOutput(null); setIsLoading(false); setProgress(0); - }; + }, []); + + const handleDownload = useCallback(() => { + if (!output) { + return; + } + const blob = new Blob([output], { type: 'text/markdown;charset=utf-8' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = downloadName; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + }, [downloadName, output]); + + const handleCopy = useCallback(() => { + if (!output) { + return; + } + navigator.clipboard.writeText(output); + }, [output]); const memoizedOptionsPanel = useMemo(() => ( - @@ -101,38 +148,50 @@ function App() { return (
-
-

CodeDump from ZIP

-

Generate a comprehensive Markdown file from your project's ZIP archive.

+
+
+
+

{t('app.title')}

+

{t('app.subtitle')}

+
+ +
-
-
+ {output && !isLoading && ( +
+ + + +
+ )} +
+
{memoizedOptionsPanel}
-
- {isLoading ? ( - - ) : output !== null ? ( - - ) : ( -
-
- -
- {zipFile && ( -
-

Selected: {zipFile.name}

- -
- )} -
- )} +
+ {isLoading ? ( + + ) : output !== null ? ( + + ) : ( +
+
+ +
+ {zipFile && ( +
+

{t('app.selectedFile', { filename: zipFile.name })}

+ +
+ )} +
+ )}
diff --git a/components/FileUpload.tsx b/components/FileUpload.tsx index 1875db9..6c9c596 100644 --- a/components/FileUpload.tsx +++ b/components/FileUpload.tsx @@ -1,6 +1,7 @@ import React, { useState, useCallback } from 'react'; import { Card } from './ui/Card'; +import { useTranslation } from '../i18n'; interface FileUploadProps { onFileSelect: (file: File) => void; @@ -16,6 +17,7 @@ const UploadIcon = () => ( export const FileUpload: React.FC = ({ onFileSelect, disabled }) => { const [isDragging, setIsDragging] = useState(false); + const { t } = useTranslation(); const handleDrag = useCallback((e: React.DragEvent) => { e.preventDefault(); @@ -55,9 +57,9 @@ export const FileUpload: React.FC = ({ onFileSelect, disabled }
diff --git a/components/LanguageSelector.tsx b/components/LanguageSelector.tsx new file mode 100644 index 0000000..14605ba --- /dev/null +++ b/components/LanguageSelector.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { useTranslation } from '../i18n'; +import { Select } from './ui/Select'; + +export const LanguageSelector: React.FC = () => { + const { language, setLanguage, t } = useTranslation(); + + const handleChange = (event: React.ChangeEvent) => { + setLanguage(event.target.value as typeof language); + }; + + return ( +
+ +
+ +
+ +
+ ); +}; diff --git a/components/OptionsPanel.tsx b/components/OptionsPanel.tsx index 5e86c13..cedf700 100644 --- a/components/OptionsPanel.tsx +++ b/components/OptionsPanel.tsx @@ -8,6 +8,9 @@ import { Select } from './ui/Select'; import { Input } from './ui/Input'; import { Textarea } from './ui/Textarea'; import { Checkbox } from './ui/Checkbox'; +import { useTranslation, TranslationKey } from '../i18n'; + +type PresetTranslationKey = `presets.${typeof PRESETS[number]['name']}`; interface OptionsPanelProps { options: DumpOptions; @@ -17,6 +20,7 @@ interface OptionsPanelProps { } export const OptionsPanel: React.FC = ({ options, setOptions, onPresetChange, disabled }) => { + const { t } = useTranslation(); const handleOptionChange = (key: K, value: DumpOptions[K]) => { setOptions(prev => ({ ...prev, [key]: value })); @@ -36,78 +40,81 @@ export const OptionsPanel: React.FC = ({ options, setOptions, return (
-

Configuration

+

{t('options.configuration')}

- +
- +