Initial commit of YouTube Downloader application, including core functionality for downloading videos, user authentication, and Docker support. Added configuration files, environment setup, and basic UI components using Astro.js and Tailwind CSS.
This commit is contained in:
101
src/lib/i18n.ts
Normal file
101
src/lib/i18n.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import type { AstroGlobal, Request } from 'astro';
|
||||
import enTranslations from '../i18n/en.json';
|
||||
import deTranslations from '../i18n/de.json';
|
||||
|
||||
type Translations = typeof enTranslations;
|
||||
|
||||
const translations: Record<string, Translations> = {
|
||||
en: enTranslations,
|
||||
de: deTranslations,
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current locale from Astro
|
||||
*/
|
||||
export function getLocale(astro: AstroGlobal): string {
|
||||
// Check environment variable first (for Docker/container environments)
|
||||
const envLocale = import.meta.env.LOCALE;
|
||||
if (envLocale && (envLocale === 'de' || envLocale === 'en')) {
|
||||
return envLocale;
|
||||
}
|
||||
return astro.locale || 'de';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get locale from Request (for API routes)
|
||||
*/
|
||||
export function getLocaleFromRequest(request: Request): string {
|
||||
// Check environment variable first (for Docker/container environments)
|
||||
const envLocale = import.meta.env.LOCALE;
|
||||
if (envLocale && (envLocale === 'de' || envLocale === 'en')) {
|
||||
return envLocale;
|
||||
}
|
||||
|
||||
// Try to get locale from Accept-Language header
|
||||
const acceptLanguage = request.headers.get('accept-language');
|
||||
if (acceptLanguage) {
|
||||
if (acceptLanguage.includes('de')) return 'de';
|
||||
if (acceptLanguage.includes('en')) return 'en';
|
||||
}
|
||||
return 'de'; // Default to German
|
||||
}
|
||||
|
||||
/**
|
||||
* Get translation for a key
|
||||
* Supports nested keys like "common.download"
|
||||
* Supports interpolation with {{variable}}
|
||||
*/
|
||||
function getTranslation(locale: string, key: string, params?: Record<string, string>): string {
|
||||
const translation = translations[locale] || translations.de;
|
||||
|
||||
// Navigate through nested keys
|
||||
const keys = key.split('.');
|
||||
let value: any = translation;
|
||||
|
||||
for (const k of keys) {
|
||||
if (value && typeof value === 'object' && k in value) {
|
||||
value = value[k];
|
||||
} else {
|
||||
// Fallback to German if key not found
|
||||
value = deTranslations;
|
||||
for (const k2 of keys) {
|
||||
if (value && typeof value === 'object' && k2 in value) {
|
||||
value = value[k2];
|
||||
} else {
|
||||
return key; // Return key if translation not found
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof value !== 'string') {
|
||||
return key;
|
||||
}
|
||||
|
||||
// Replace placeholders like {{variable}}
|
||||
if (params) {
|
||||
return value.replace(/\{\{(\w+)\}\}/g, (match, paramKey) => {
|
||||
return params[paramKey] || match;
|
||||
});
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get translation in Astro component
|
||||
*/
|
||||
export function t(astro: AstroGlobal, key: string, params?: Record<string, string>): string {
|
||||
const locale = getLocale(astro);
|
||||
return getTranslation(locale, key, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get translation in API route
|
||||
*/
|
||||
export function tApi(request: Request, key: string, params?: Record<string, string>): string {
|
||||
const locale = getLocaleFromRequest(request);
|
||||
return getTranslation(locale, key, params);
|
||||
}
|
||||
|
||||
55
src/lib/session.ts
Normal file
55
src/lib/session.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import type { Request } from 'astro';
|
||||
|
||||
const SESSION_COOKIE = 'session';
|
||||
const SESSION_SECRET = import.meta.env.SESSION_SECRET || 'default-secret-change-in-production';
|
||||
|
||||
export interface Session {
|
||||
username: string;
|
||||
loggedIn: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft ob Login aktiviert ist
|
||||
* Wenn LOGIN=false oder nicht gesetzt, ist Login deaktiviert
|
||||
*/
|
||||
export function isLoginEnabled(): boolean {
|
||||
const loginEnabled = import.meta.env.LOGIN;
|
||||
// Wenn LOGIN explizit auf "false" gesetzt ist, ist Login deaktiviert
|
||||
// Ansonsten ist Login aktiviert (Standard-Verhalten)
|
||||
return loginEnabled !== "false";
|
||||
}
|
||||
|
||||
export async function getSession(request: Request): Promise<Session | null> {
|
||||
const cookie = request.headers.get('cookie');
|
||||
if (!cookie) return null;
|
||||
|
||||
const sessionCookie = cookie
|
||||
.split(';')
|
||||
.find(c => c.trim().startsWith(`${SESSION_COOKIE}=`));
|
||||
|
||||
if (!sessionCookie) return null;
|
||||
|
||||
const sessionValue = sessionCookie.split('=')[1];
|
||||
|
||||
// Einfache Session-Validierung (in Produktion sollte man hier eine echte Session-Verwaltung verwenden)
|
||||
try {
|
||||
const session = JSON.parse(decodeURIComponent(sessionValue));
|
||||
if (session.loggedIn && session.username) {
|
||||
return session;
|
||||
}
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function createSessionCookie(session: Session): string {
|
||||
const sessionValue = JSON.stringify(session);
|
||||
return `${SESSION_COOKIE}=${encodeURIComponent(sessionValue)}; HttpOnly; Path=/; Max-Age=86400; SameSite=Lax`;
|
||||
}
|
||||
|
||||
export function clearSessionCookie(): string {
|
||||
return `${SESSION_COOKIE}=; HttpOnly; Path=/; Max-Age=0; SameSite=Lax`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user