-
+
+
);
diff --git a/electron/main.cjs b/electron/main.cjs
new file mode 100644
index 0000000..8d69477
--- /dev/null
+++ b/electron/main.cjs
@@ -0,0 +1,53 @@
+const { app, BrowserWindow, shell } = require('electron');
+const path = require('path');
+
+if (require('electron-squirrel-startup')) {
+ app.quit();
+}
+
+const createWindow = () => {
+ const mainWindow = new BrowserWindow({
+ width: 1280,
+ height: 800,
+ show: false,
+ autoHideMenuBar: true,
+ webPreferences: {
+ preload: path.join(__dirname, 'preload.cjs'),
+ contextIsolation: true,
+ },
+ });
+
+ mainWindow.once('ready-to-show', () => {
+ mainWindow.show();
+ });
+
+ if (app.isPackaged) {
+ const indexPath = path.join(__dirname, '..', 'dist', 'index.html');
+ mainWindow.loadFile(indexPath);
+ } else {
+ const devServer = process.env.VITE_DEV_SERVER_URL || 'http://localhost:5173';
+ mainWindow.loadURL(devServer);
+ mainWindow.webContents.openDevTools({ mode: 'detach' });
+ }
+
+ mainWindow.webContents.setWindowOpenHandler(({ url }) => {
+ shell.openExternal(url);
+ return { action: 'deny' };
+ });
+};
+
+app.whenReady().then(() => {
+ createWindow();
+
+ app.on('activate', () => {
+ if (BrowserWindow.getAllWindows().length === 0) {
+ createWindow();
+ }
+ });
+});
+
+app.on('window-all-closed', () => {
+ if (process.platform !== 'darwin') {
+ app.quit();
+ }
+});
diff --git a/electron/preload.cjs b/electron/preload.cjs
new file mode 100644
index 0000000..9d71c64
--- /dev/null
+++ b/electron/preload.cjs
@@ -0,0 +1,5 @@
+const { contextBridge } = require('electron');
+
+contextBridge.exposeInMainWorld('appInfo', {
+ version: process.versions.electron,
+});
diff --git a/i18n.tsx b/i18n.tsx
new file mode 100644
index 0000000..81ad773
--- /dev/null
+++ b/i18n.tsx
@@ -0,0 +1,166 @@
+import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
+
+const translations = {
+ en: {
+ 'app.title': 'CodeDump from ZIP',
+ 'app.subtitle': 'Generate a comprehensive Markdown file from your project\'s ZIP archive.',
+ 'app.selectedFile': 'Selected: {filename}',
+ 'app.generate': 'Generate CodeDump',
+ 'app.invalidZip': 'Please select a valid ZIP file.',
+ 'app.noZip': 'Please select a ZIP file first.',
+ 'app.error': 'An error occurred: {message}',
+ 'loading.processing': 'Processing ZIP file...',
+ 'loading.percentComplete': '{percent}% complete',
+ 'options.configuration': 'Configuration',
+ 'options.preset': 'Preset',
+ 'options.preset.custom': 'Custom',
+ 'options.include.label': 'Include Patterns (one per line)',
+ 'options.include.placeholder': '**/*.js\n**/*.tsx',
+ 'options.exclude.label': 'Exclude Patterns (one per line)',
+ 'options.exclude.placeholder': '**/node_modules/**\n**/dist/**',
+ 'options.maxSize.label': 'Max File Size (bytes)',
+ 'options.maxSize.placeholder': 'e.g., 1000000 for 1MB',
+ 'options.binaryMode.label': 'Binary Files',
+ 'options.binaryMode.skip': 'Skip',
+ 'options.binaryMode.hex': 'Hex',
+ 'options.binaryMode.base64': 'Base64',
+ 'options.binaryMode.bytes': 'Bytes',
+ 'options.sort.label': 'Sort By',
+ 'options.sort.path': 'Path',
+ 'options.sort.size': 'Size',
+ 'options.sort.time': 'Time',
+ 'options.showMetadata': 'Show File Metadata',
+ 'options.strictText': 'Strict Text Decoding',
+ 'output.title': 'Generated CodeDump',
+ 'output.copy': 'Copy',
+ 'output.download': 'Download',
+ 'output.newDump': 'New Dump',
+ 'fileUpload.callToAction': 'Upload a ZIP file',
+ 'fileUpload.orDragDrop': 'or drag and drop',
+ 'fileUpload.hint': 'ZIP archive of your project',
+ 'language.selector': 'Language',
+ 'language.en': 'English',
+ 'language.de': 'Deutsch',
+ 'presets.full-stack': 'Full Stack',
+ 'presets.python-web': 'Python Web',
+ 'presets.react': 'React',
+ 'presets.node-app': 'Node App',
+ 'presets.data-science': 'Data Science',
+ 'presets.rust-crate': 'Rust Crate',
+ 'presets.java-maven': 'Java Maven',
+ 'presets.dotnet': '.NET'
+ },
+ de: {
+ 'app.title': 'CodeDump aus ZIP',
+ 'app.subtitle': 'Erzeuge eine umfassende Markdown-Datei aus dem ZIP-Archiv deines Projekts.',
+ 'app.selectedFile': 'Ausgewählt: {filename}',
+ 'app.generate': 'CodeDump erstellen',
+ 'app.invalidZip': 'Bitte wähle eine gültige ZIP-Datei aus.',
+ 'app.noZip': 'Bitte wähle zuerst eine ZIP-Datei aus.',
+ 'app.error': 'Es ist ein Fehler aufgetreten: {message}',
+ 'loading.processing': 'ZIP-Datei wird verarbeitet...',
+ 'loading.percentComplete': '{percent}% abgeschlossen',
+ 'options.configuration': 'Konfiguration',
+ 'options.preset': 'Voreinstellung',
+ 'options.preset.custom': 'Benutzerdefiniert',
+ 'options.include.label': 'Einschlussmuster (je Zeile eines)',
+ 'options.include.placeholder': '**/*.js\n**/*.tsx',
+ 'options.exclude.label': 'Ausschlussmuster (je Zeile eines)',
+ 'options.exclude.placeholder': '**/node_modules/**\n**/dist/**',
+ 'options.maxSize.label': 'Maximale Dateigröße (Bytes)',
+ 'options.maxSize.placeholder': 'z. B. 1000000 für 1 MB',
+ 'options.binaryMode.label': 'Binärdateien',
+ 'options.binaryMode.skip': 'Überspringen',
+ 'options.binaryMode.hex': 'Hexadezimal',
+ 'options.binaryMode.base64': 'Base64',
+ 'options.binaryMode.bytes': 'Bytes',
+ 'options.sort.label': 'Sortieren nach',
+ 'options.sort.path': 'Pfad',
+ 'options.sort.size': 'Größe',
+ 'options.sort.time': 'Zeit',
+ 'options.showMetadata': 'Dateimetadaten anzeigen',
+ 'options.strictText': 'Strikte Textdekodierung',
+ 'output.title': 'Erstellter CodeDump',
+ 'output.copy': 'Kopieren',
+ 'output.download': 'Herunterladen',
+ 'output.newDump': 'Neuer Dump',
+ 'fileUpload.callToAction': 'ZIP-Datei hochladen',
+ 'fileUpload.orDragDrop': 'oder per Drag & Drop hinzufügen',
+ 'fileUpload.hint': 'ZIP-Archiv deines Projekts',
+ 'language.selector': 'Sprache',
+ 'language.en': 'Englisch',
+ 'language.de': 'Deutsch',
+ 'presets.full-stack': 'Full Stack',
+ 'presets.python-web': 'Python Web',
+ 'presets.react': 'React',
+ 'presets.node-app': 'Node-App',
+ 'presets.data-science': 'Data Science',
+ 'presets.rust-crate': 'Rust-Projekt',
+ 'presets.java-maven': 'Java Maven',
+ 'presets.dotnet': '.NET'
+ }
+} as const;
+
+type LanguageCode = keyof typeof translations;
+type TranslationKey = keyof typeof translations.en;
+type TranslationValues = Record
;
+
+interface TranslationContextValue {
+ language: LanguageCode;
+ setLanguage: (language: LanguageCode) => void;
+ t: (key: TranslationKey, values?: TranslationValues) => string;
+}
+
+const TranslationContext = createContext(undefined);
+
+function getInitialLanguage(): LanguageCode {
+ if (typeof window !== 'undefined') {
+ const stored = window.localStorage.getItem('language');
+ if (stored === 'en' || stored === 'de') {
+ return stored;
+ }
+ const navigatorLang = window.navigator.language.slice(0, 2);
+ if (navigatorLang === 'de') {
+ return 'de';
+ }
+ }
+ return 'en';
+}
+
+export const TranslationProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+ const [language, setLanguage] = useState(() => getInitialLanguage());
+
+ useEffect(() => {
+ if (typeof window !== 'undefined') {
+ window.localStorage.setItem('language', language);
+ }
+ }, [language]);
+
+ const translate = useCallback((key: TranslationKey, values: TranslationValues = {}) => {
+ const template = translations[language][key] ?? translations.en[key] ?? key;
+ return template.replace(/\{(\w+)\}/g, (_, token: string) => {
+ if (token in values) {
+ return String(values[token]);
+ }
+ return '';
+ });
+ }, [language]);
+
+ const value = useMemo(() => ({ language, setLanguage, t: translate }), [language, translate]);
+
+ return (
+
+ {children}
+
+ );
+};
+
+export function useTranslation(): TranslationContextValue {
+ const context = useContext(TranslationContext);
+ if (!context) {
+ throw new Error('useTranslation must be used within a TranslationProvider');
+ }
+ return context;
+}
+
+export type { LanguageCode, TranslationKey };
diff --git a/index.tsx b/index.tsx
index aaa0c6e..3c2972a 100644
--- a/index.tsx
+++ b/index.tsx
@@ -2,6 +2,7 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
+import { TranslationProvider } from './i18n';
const rootElement = document.getElementById('root');
if (!rootElement) {
@@ -11,6 +12,8 @@ if (!rootElement) {
const root = ReactDOM.createRoot(rootElement);
root.render(
-
+
+
+
);
diff --git a/package-lock.json b/package-lock.json
index 18a634f..43dd4fe 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,6 +15,9 @@
"devDependencies": {
"@types/node": "^22.14.0",
"@vitejs/plugin-react": "^5.0.0",
+ "electron": "^30.0.0",
+ "electron-builder": "^25.1.8",
+ "electron-squirrel-startup": "^1.0.0",
"typescript": "~5.8.2",
"vite": "^6.2.0"
}
diff --git a/package.json b/package.json
index 4b7625b..6836996 100644
--- a/package.json
+++ b/package.json
@@ -3,9 +3,12 @@
"private": true,
"version": "0.0.0",
"type": "module",
+ "main": "electron/main.cjs",
"scripts": {
"dev": "vite",
- "build": "vite build",
+ "build:web": "vite build",
+ "build:electron": "electron-builder --win",
+ "build": "npm run build:web && npm run build:electron",
"preview": "vite preview"
},
"dependencies": {
@@ -16,7 +19,39 @@
"devDependencies": {
"@types/node": "^22.14.0",
"@vitejs/plugin-react": "^5.0.0",
+ "electron": "^30.0.0",
+ "electron-builder": "^25.1.8",
+ "electron-squirrel-startup": "^1.0.0",
"typescript": "~5.8.2",
"vite": "^6.2.0"
+ },
+ "build": {
+ "appId": "com.codedump.desktop",
+ "productName": "CodeDump",
+ "directories": {
+ "output": "release",
+ "buildResources": "build"
+ },
+ "files": [
+ "dist/**/*",
+ "electron/**/*",
+ "package.json"
+ ],
+ "win": {
+ "target": [
+ {
+ "target": "nsis",
+ "arch": [
+ "x64"
+ ]
+ }
+ ]
+ },
+ "nsis": {
+ "oneClick": true,
+ "perMachine": false,
+ "allowElevation": true,
+ "allowToChangeInstallationDirectory": false
+ }
}
}