import { Check, Clipboard, FileDown, FileInput, Languages, Maximize2, Minus, Plus, RefreshCcw, Settings, ShieldCheck, SunMoon, Trash2, X } from "lucide-react"; import { useEffect, useMemo, useState } from "react"; import type { PointerEvent, ReactNode } from "react"; import packageInfo from "../package.json"; import { transformEnv } from "./env"; import type { EnvDefault } from "./env"; type ThemeMode = "system" | "light" | "dark"; type Language = "de" | "en" | "es" | "fr" | "nl"; const sampleEnv = `APP_IMAGE=git.wilkensxl.de/mrsphay/hilden-directory-gateway:latest APP_PORT=3000 NODE_ENV=production PUBLIC_BASE_URL=https://gateway.example.local TRUSTED_IFRAME_ANCESTORS=https://www.hilden.de DATABASE_URL=postgresql://hilden_app:CHANGE_ME_POSTGRES_PASSWORD@postgres:5432/hilden_directory POSTGRES_PASSWORD=CHANGE_ME_POSTGRES_PASSWORD SESSION_SECRET=CHANGE_ME_AT_LEAST_32_RANDOM_CHARACTERS ENCRYPTION_KEY_BASE64=CHANGE_ME_32_RANDOM_BYTES_AS_BASE64 BOOTSTRAP_ADMIN_EMAIL=admin@example.local BOOTSTRAP_ADMIN_PASSWORD=CHANGE_ME_LONG_INITIAL_ADMIN_PASSWORD`; const initialDefaults: EnvDefault[] = [{ key: "BOOTSTRAP_ADMIN_EMAIL", value: "admin@example.local" }]; const languageNames: Record = { de: "Deutsch", en: "English", es: "Español", fr: "Français", nl: "Nederlands" }; const translations = { de: { appSubtitle: "Lokaler Helfer für .env Vorlagen", loadFile: "Datei laden", saveFile: ".env speichern", settings: "Einstellungen", input: "Input", output: "Output", inputHint: "Datei laden oder Text direkt bearbeiten", outputHint: "Generierte .env, bereit zum Kopieren", copy: "Kopieren", copied: "Kopiert", regenerate: "Neu erzeugen", sample: "Beispiel laden", hits: "Fundstellen", values: "Werte", defaultsApplied: "Defaults", source: "Quelle", text: "Text", defaults: "Standardwerte", defaultsHint: "Diese Werte überschreiben passende Keys im Output.", addDefault: "Standardwert hinzufügen", key: "Key", value: "Wert", detected: "Erkannte Anforderungen", detectedHint: "Placeholder, betroffene Keys und erzeugtes Format.", placeholder: "Placeholder", keys: "Keys", format: "Format", noPlaceholders: "Keine CHANGE_ME Platzhalter gefunden.", language: "Sprache", theme: "Darstellung", system: "System", light: "Hell", dark: "Dunkel", version: "Version", author: "Autor", repository: "Repository" }, en: { appSubtitle: "Local helper for .env templates", loadFile: "Load file", saveFile: "Save .env", settings: "Settings", input: "Input", output: "Output", inputHint: "Load a file or edit text directly", outputHint: "Generated .env, ready to copy", copy: "Copy", copied: "Copied", regenerate: "Regenerate", sample: "Load sample", hits: "Matches", values: "Values", defaultsApplied: "Defaults", source: "Source", text: "Text", defaults: "Defaults", defaultsHint: "These values override matching keys in the output.", addDefault: "Add default", key: "Key", value: "Value", detected: "Detected requirements", detectedHint: "Placeholder, affected keys, and generated format.", placeholder: "Placeholder", keys: "Keys", format: "Format", noPlaceholders: "No CHANGE_ME placeholders found.", language: "Language", theme: "Appearance", system: "System", light: "Light", dark: "Dark", version: "Version", author: "Author", repository: "Repository" }, es: { appSubtitle: "Ayudante local para plantillas .env", loadFile: "Cargar archivo", saveFile: "Guardar .env", settings: "Ajustes", input: "Entrada", output: "Salida", inputHint: "Carga un archivo o edita el texto", outputHint: ".env generado, listo para copiar", copy: "Copiar", copied: "Copiado", regenerate: "Regenerar", sample: "Cargar ejemplo", hits: "Coincidencias", values: "Valores", defaultsApplied: "Defaults", source: "Origen", text: "Texto", defaults: "Valores predeterminados", defaultsHint: "Estos valores sobrescriben claves iguales en la salida.", addDefault: "Añadir valor", key: "Clave", value: "Valor", detected: "Requisitos detectados", detectedHint: "Placeholder, claves afectadas y formato generado.", placeholder: "Placeholder", keys: "Claves", format: "Formato", noPlaceholders: "No se encontraron placeholders CHANGE_ME.", language: "Idioma", theme: "Apariencia", system: "Sistema", light: "Claro", dark: "Oscuro", version: "Versión", author: "Autor", repository: "Repositorio" }, fr: { appSubtitle: "Assistant local pour modèles .env", loadFile: "Charger", saveFile: "Enregistrer .env", settings: "Réglages", input: "Entrée", output: "Sortie", inputHint: "Chargez un fichier ou modifiez le texte", outputHint: ".env généré, prêt à copier", copy: "Copier", copied: "Copié", regenerate: "Régénérer", sample: "Charger exemple", hits: "Occurrences", values: "Valeurs", defaultsApplied: "Défauts", source: "Source", text: "Texte", defaults: "Valeurs par défaut", defaultsHint: "Ces valeurs remplacent les clés correspondantes dans la sortie.", addDefault: "Ajouter", key: "Clé", value: "Valeur", detected: "Exigences détectées", detectedHint: "Placeholder, clés concernées et format généré.", placeholder: "Placeholder", keys: "Clés", format: "Format", noPlaceholders: "Aucun placeholder CHANGE_ME trouvé.", language: "Langue", theme: "Apparence", system: "Système", light: "Clair", dark: "Sombre", version: "Version", author: "Auteur", repository: "Dépôt" }, nl: { appSubtitle: "Lokale helper voor .env sjablonen", loadFile: "Bestand laden", saveFile: ".env opslaan", settings: "Instellingen", input: "Input", output: "Output", inputHint: "Laad een bestand of bewerk tekst direct", outputHint: "Gegenereerde .env, klaar om te kopiëren", copy: "Kopiëren", copied: "Gekopieerd", regenerate: "Opnieuw maken", sample: "Voorbeeld laden", hits: "Treffers", values: "Waarden", defaultsApplied: "Defaults", source: "Bron", text: "Tekst", defaults: "Standaardwaarden", defaultsHint: "Deze waarden overschrijven gelijke keys in de output.", addDefault: "Waarde toevoegen", key: "Key", value: "Waarde", detected: "Gedetecteerde eisen", detectedHint: "Placeholder, betrokken keys en gegenereerd formaat.", placeholder: "Placeholder", keys: "Keys", format: "Formaat", noPlaceholders: "Geen CHANGE_ME placeholders gevonden.", language: "Taal", theme: "Weergave", system: "Systeem", light: "Licht", dark: "Donker", version: "Versie", author: "Auteur", repository: "Repository" } } satisfies Record>; const themeLabels: ThemeMode[] = ["system", "light", "dark"]; function detectLanguage(): Language { const stored = localStorage.getItem("envhelper-language") as Language | null; if (stored && stored in languageNames) { return stored; } const browserLanguage = navigator.language.slice(0, 2) as Language; return browserLanguage in languageNames ? browserLanguage : "de"; } function readDefaults(): EnvDefault[] { const stored = localStorage.getItem("envhelper-defaults"); if (!stored) { return initialDefaults; } try { const parsed = JSON.parse(stored) as EnvDefault[]; return Array.isArray(parsed) ? parsed : initialDefaults; } catch { return initialDefaults; } } function readTheme(): ThemeMode { const stored = localStorage.getItem("envhelper-theme") as ThemeMode | null; return stored && themeLabels.includes(stored) ? stored : "system"; } export default function App() { const [input, setInput] = useState(sampleEnv); const [loadedPath, setLoadedPath] = useState(null); const [copyLabel, setCopyLabel] = useState<"copy" | "copied">("copy"); const [generation, setGeneration] = useState(0); const [settingsOpen, setSettingsOpen] = useState(false); const [themeMode, setThemeMode] = useState(readTheme); const [language, setLanguage] = useState(detectLanguage); const [defaults, setDefaults] = useState(readDefaults); const t = translations[language]; const result = useMemo(() => transformEnv(input, defaults), [input, defaults, generation]); useEffect(() => { document.documentElement.dataset.theme = themeMode; localStorage.setItem("envhelper-theme", themeMode); }, [themeMode]); useEffect(() => { localStorage.setItem("envhelper-language", language); }, [language]); useEffect(() => { localStorage.setItem("envhelper-defaults", JSON.stringify(defaults)); }, [defaults]); async function openFile() { const file = await window.envHelper?.openFile(); if (file) { setInput(file.content); setLoadedPath(file.path); } } async function saveFile() { await window.envHelper?.saveFile(result.output); } async function copyOutput() { await navigator.clipboard.writeText(result.output); setCopyLabel("copied"); window.setTimeout(() => setCopyLabel("copy"), 1400); } function updateDefault(index: number, field: keyof EnvDefault, value: string) { setDefaults((current) => current.map((entry, entryIndex) => (entryIndex === index ? { ...entry, [field]: value } : entry))); } function addDefault() { setDefaults((current) => [...current, { key: "", value: "" }]); } function removeDefault(index: number) { setDefaults((current) => current.filter((_, entryIndex) => entryIndex !== index)); } function runWindowControl(event: PointerEvent, action?: () => void) { event.preventDefault(); event.stopPropagation(); action?.(); } return (
EH
EnvHelper
EH

EnvHelper

{t.appSubtitle}

{themeLabels.map((mode) => ( ))}
setInput(sampleEnv)} title={t.sample} type="button"> } hint={loadedPath ?? t.inputHint} title={t.input} >