feat(i18n): add language picker and 5-language support to all auth screens

- Add svelte-i18n and @manacore/shared-i18n to Chat, Presi, Zitare
- Create LanguageSelector component for each app
- Add locale files for DE, EN, IT, FR, ES in all apps
- Update auth pages (login, register, forgot-password) to use shared translations
- Add headerControls snippet to RegisterPage and ForgotPasswordPage
- Export ZitareLogo and NutriPhiLogo from shared-branding

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Till-JS 2025-11-30 00:28:14 +01:00
parent e85ef8babe
commit eab69c512c
41 changed files with 626 additions and 142 deletions

View file

@ -42,6 +42,7 @@
"@manacore/shared-profile-ui": "workspace:*",
"@manacore/shared-ui": "workspace:*",
"@manacore/shared-utils": "workspace:*",
"marked": "^17.0.0"
"marked": "^17.0.0",
"svelte-i18n": "^4.0.1"
}
}

View file

@ -0,0 +1,20 @@
<script lang="ts">
import { locale } from 'svelte-i18n';
import { LanguageSelector } from '@manacore/shared-i18n';
import { setLocale, supportedLocales } from '$lib/i18n';
import { theme } from '$lib/stores/theme';
let currentLocale = $derived($locale || 'de');
function handleLocaleChange(newLocale: string) {
setLocale(newLocale as any);
}
</script>
<LanguageSelector
{currentLocale}
{supportedLocales}
onLocaleChange={handleLocaleChange}
isDark={theme.isDark}
primaryColor="#0ea5e9"
/>

View file

@ -0,0 +1,52 @@
import { browser } from '$app/environment';
import { init, register, locale, waitLocale } from 'svelte-i18n';
// List of supported locales
export const supportedLocales = ['de', 'en', 'it', 'fr', 'es'] as const;
export type SupportedLocale = (typeof supportedLocales)[number];
// Default locale
const defaultLocale = 'de';
// Register all available locales
register('de', () => import('./locales/de.json'));
register('en', () => import('./locales/en.json'));
register('it', () => import('./locales/it.json'));
register('fr', () => import('./locales/fr.json'));
register('es', () => import('./locales/es.json'));
// Get initial locale from browser or localStorage
function getInitialLocale(): SupportedLocale {
if (browser) {
// Check localStorage first
const stored = localStorage.getItem('chat_locale');
if (stored && supportedLocales.includes(stored as SupportedLocale)) {
return stored as SupportedLocale;
}
// Fall back to browser language
const browserLang = navigator.language.split('-')[0];
if (supportedLocales.includes(browserLang as SupportedLocale)) {
return browserLang as SupportedLocale;
}
}
return defaultLocale;
}
// Initialize i18n at module scope (required for SSR)
init({
fallbackLocale: defaultLocale,
initialLocale: getInitialLocale(),
});
// Set locale and persist to localStorage
export function setLocale(newLocale: SupportedLocale) {
locale.set(newLocale);
if (browser) {
localStorage.setItem('chat_locale', newLocale);
}
}
// Wait for locale to be loaded (useful for SSR)
export { waitLocale };

View file

@ -0,0 +1,13 @@
{
"app_slider": {
"title": "Weitere Manacore Apps",
"memoro_desc": "KI-gestützte Sprachmemos",
"maerchenzauber_desc": "Magische Gute-Nacht-Geschichten",
"manadeck_desc": "KI Lernkarten",
"picture_desc": "KI Bildgenerierung",
"moodlit_desc": "Dein Stimmungsbegleiter",
"manacore_desc": "KI-Produktivitätssuite",
"coming_soon": "Demnächst",
"download": "Download"
}
}

View file

@ -0,0 +1,13 @@
{
"app_slider": {
"title": "More Manacore Apps",
"memoro_desc": "AI-powered voice memos",
"maerchenzauber_desc": "Magical bedtime stories",
"manadeck_desc": "AI flashcards",
"picture_desc": "AI image generation",
"moodlit_desc": "Your mood companion",
"manacore_desc": "AI productivity suite",
"coming_soon": "Coming soon",
"download": "Download"
}
}

View file

@ -0,0 +1,13 @@
{
"app_slider": {
"title": "Más Apps de Manacore",
"memoro_desc": "Notas de voz con IA",
"maerchenzauber_desc": "Cuentos mágicos para dormir",
"manadeck_desc": "Flashcards con IA",
"picture_desc": "Generación de imágenes con IA",
"moodlit_desc": "Tu compañero de ánimo",
"manacore_desc": "Suite de productividad IA",
"coming_soon": "Próximamente",
"download": "Descargar"
}
}

View file

@ -0,0 +1,13 @@
{
"app_slider": {
"title": "Autres Apps Manacore",
"memoro_desc": "Mémos vocaux avec IA",
"maerchenzauber_desc": "Histoires magiques du soir",
"manadeck_desc": "Flashcards avec IA",
"picture_desc": "Génération d'images avec IA",
"moodlit_desc": "Ton compagnon d'humeur",
"manacore_desc": "Suite de productivité IA",
"coming_soon": "Bientôt disponible",
"download": "Télécharger"
}
}

View file

@ -0,0 +1,13 @@
{
"app_slider": {
"title": "Altre App Manacore",
"memoro_desc": "Memo vocali con IA",
"maerchenzauber_desc": "Storie magiche della buonanotte",
"manadeck_desc": "Flashcard con IA",
"picture_desc": "Generazione immagini con IA",
"moodlit_desc": "Il tuo compagno d'umore",
"manacore_desc": "Suite di produttività IA",
"coming_soon": "Prossimamente",
"download": "Scarica"
}
}

View file

@ -1,26 +1,16 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { locale } from 'svelte-i18n';
import { ForgotPasswordPage } from '@manacore/shared-auth-ui';
import { getForgotPasswordTranslations } from '@manacore/shared-i18n';
import { ChatLogo } from '@manacore/shared-branding';
import { authStore } from '$lib/stores/auth.svelte';
import AppSlider from '$lib/components/AppSlider.svelte';
import LanguageSelector from '$lib/components/LanguageSelector.svelte';
import '$lib/i18n';
// German translations
const translations = {
titleForm: 'Passwort zurücksetzen',
titleSuccess: 'E-Mail gesendet',
description:
'Gib deine E-Mail-Adresse ein und wir senden dir einen Link zum Zurücksetzen deines Passworts.',
emailPlaceholder: 'E-Mail',
sendResetLinkButton: 'Link senden',
sending: 'Wird gesendet...',
backToLogin: 'Zurück zur Anmeldung',
resendEmail: 'E-Mail erneut senden',
successMessage:
'Wir haben einen Link zum Zurücksetzen deines Passworts an {email} gesendet. Bitte überprüfe deinen Posteingang.',
emailRequired: 'E-Mail ist erforderlich',
sendFailed: 'Fehler beim Senden der E-Mail',
};
// Get translations based on current locale
const translations = $derived(getForgotPasswordTranslations($locale || 'de'));
async function handleForgotPassword(email: string) {
return authStore.resetPassword(email);
@ -28,7 +18,7 @@
</script>
<svelte:head>
<title>Passwort zurücksetzen | ManaChat</title>
<title>{translations.titleForm} | ManaChat</title>
</svelte:head>
<ForgotPasswordPage
@ -42,6 +32,9 @@
darkBackground="#0c1929"
{translations}
>
{#snippet headerControls()}
<LanguageSelector />
{/snippet}
{#snippet appSlider()}
<AppSlider />
{/snippet}

View file

@ -1,39 +1,20 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { locale } from 'svelte-i18n';
import { LoginPage } from '@manacore/shared-auth-ui';
import { getLoginTranslations } from '@manacore/shared-i18n';
import { ChatLogo } from '@manacore/shared-branding';
import { authStore } from '$lib/stores/auth.svelte';
import AppSlider from '$lib/components/AppSlider.svelte';
import LanguageSelector from '$lib/components/LanguageSelector.svelte';
import '$lib/i18n';
// Get redirect URL from query params
const redirectTo = $derived($page.url.searchParams.get('redirectTo') || '/chat');
// German translations
const translations = {
title: 'Anmelden',
subtitle: 'Melde dich mit deinem Konto an',
emailPlaceholder: 'E-Mail',
passwordPlaceholder: 'Passwort',
rememberMe: 'Angemeldet bleiben',
forgotPassword: 'Passwort vergessen?',
signInButton: 'Anmelden',
signingIn: 'Wird angemeldet...',
success: 'Erfolgreich!',
orDivider: 'oder',
noAccount: 'Noch kein Konto?',
createAccount: 'Jetzt registrieren',
skipToForm: 'Zum Login-Formular springen',
showPassword: 'Passwort anzeigen',
hidePassword: 'Passwort verbergen',
emailRequired: 'E-Mail ist erforderlich',
emailInvalid: 'Bitte gib eine gültige E-Mail-Adresse ein',
passwordRequired: 'Passwort ist erforderlich',
signInFailed: 'Anmeldung fehlgeschlagen',
googleSignInFailed: 'Google-Anmeldung fehlgeschlagen',
signInSuccess: 'Erfolgreich angemeldet. Weiterleitung...',
googleSignInSuccess: 'Erfolgreich mit Google angemeldet. Weiterleitung...',
};
// Get translations based on current locale
const translations = $derived(getLoginTranslations($locale || 'de'));
async function handleSignIn(email: string, password: string) {
return authStore.signIn(email, password);
@ -41,7 +22,7 @@
</script>
<svelte:head>
<title>Anmelden | ManaChat</title>
<title>{translations.title} | ManaChat</title>
</svelte:head>
<LoginPage
@ -59,6 +40,9 @@
darkBackground="#0c1929"
{translations}
>
{#snippet headerControls()}
<LanguageSelector />
{/snippet}
{#snippet appSlider()}
<AppSlider />
{/snippet}

View file

@ -1,33 +1,16 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { locale } from 'svelte-i18n';
import { RegisterPage } from '@manacore/shared-auth-ui';
import { getRegisterTranslations } from '@manacore/shared-i18n';
import { ChatLogo } from '@manacore/shared-branding';
import { authStore } from '$lib/stores/auth.svelte';
import AppSlider from '$lib/components/AppSlider.svelte';
import LanguageSelector from '$lib/components/LanguageSelector.svelte';
import '$lib/i18n';
// German translations
const translations = {
title: 'Konto erstellen',
emailPlaceholder: 'E-Mail',
passwordPlaceholder: 'Passwort',
confirmPasswordPlaceholder: 'Passwort bestätigen',
passwordRequirements:
'Passwort muss mindestens 8 Zeichen mit Kleinbuchstaben, Großbuchstaben, Zahl und Sonderzeichen enthalten.',
createAccountButton: 'Konto erstellen',
creatingAccount: 'Wird erstellt...',
backToLogin: 'Zurück zur Anmeldung',
showPassword: 'Passwort anzeigen',
hidePassword: 'Passwort verbergen',
emailRequired: 'E-Mail ist erforderlich',
passwordRequired: 'Passwort ist erforderlich',
confirmPasswordRequired: 'Bitte bestätige dein Passwort',
passwordsDoNotMatch: 'Passwörter stimmen nicht überein',
passwordTooShort: 'Passwort muss mindestens 8 Zeichen lang sein',
passwordStrengthError:
'Passwort muss Kleinbuchstaben, Großbuchstaben, Zahl und Sonderzeichen enthalten',
registrationFailed: 'Registrierung fehlgeschlagen',
accountCreated: 'Konto erstellt! Bitte überprüfe deine E-Mails zur Bestätigung.',
};
// Get translations based on current locale
const translations = $derived(getRegisterTranslations($locale || 'de'));
async function handleSignUp(email: string, password: string) {
return authStore.signUp(email, password);
@ -35,7 +18,7 @@
</script>
<svelte:head>
<title>Registrieren | ManaChat</title>
<title>{translations.title} | ManaChat</title>
</svelte:head>
<RegisterPage
@ -50,6 +33,9 @@
darkBackground="#0c1929"
{translations}
>
{#snippet headerControls()}
<LanguageSelector />
{/snippet}
{#snippet appSlider()}
<AppSlider />
{/snippet}

View file

@ -2,7 +2,7 @@ import { browser } from '$app/environment';
import { init, register, locale, waitLocale } from 'svelte-i18n';
// List of supported locales
export const supportedLocales = ['de', 'en'] as const;
export const supportedLocales = ['de', 'en', 'it', 'fr', 'es'] as const;
export type SupportedLocale = (typeof supportedLocales)[number];
// Default locale
@ -11,6 +11,9 @@ const defaultLocale = 'de';
// Register all available locales
register('de', () => import('./locales/de.json'));
register('en', () => import('./locales/en.json'));
register('it', () => import('./locales/it.json'));
register('fr', () => import('./locales/fr.json'));
register('es', () => import('./locales/es.json'));
// Get initial locale from browser or localStorage
function getInitialLocale(): SupportedLocale {

View file

@ -0,0 +1,23 @@
{
"app_slider": {
"title": "Más Apps de Manacore",
"memoro_desc": "Notas de voz con IA",
"memoro_long_desc": "Transforma tu voz en notas organizadas y accionables con transcripción y análisis impulsados por IA. Perfecto para capturar ideas en movimiento.",
"maerchenzauber_desc": "Cuentos mágicos para dormir",
"maerchenzauber_long_desc": "Crea cuentos personalizados para dormir para tus hijos con IA. Enciende la imaginación y haz cada noche mágica con narrativas únicas.",
"manadeck_desc": "Flashcards con IA",
"manadeck_long_desc": "Crea y estudia con flashcards inteligentes y repetición espaciada impulsada por IA.",
"picture_desc": "Generación de imágenes con IA",
"picture_long_desc": "Crea imágenes impresionantes con IA. Transforma tus ideas en obras de arte visuales en segundos.",
"moodlit_desc": "Tu compañero de ánimo",
"moodlit_long_desc": "Rastrea y comprende tus emociones con análisis impulsados por IA. Desarrolla conciencia emocional y mejora tu bienestar mental.",
"manacore_desc": "Suite de productividad IA",
"manacore_long_desc": "El centro para todas las apps de Manacore. Gestiona tus suscripciones, sincroniza datos y accede a potentes herramientas de IA desde un solo lugar.",
"coming_soon": "Próximamente",
"download": "Descargar",
"status_published": "Publicado",
"status_beta": "Beta",
"status_development": "En desarrollo",
"status_planning": "Planificado"
}
}

View file

@ -0,0 +1,23 @@
{
"app_slider": {
"title": "Autres Apps Manacore",
"memoro_desc": "Mémos vocaux avec IA",
"memoro_long_desc": "Transformez votre voix en notes organisées et exploitables grâce à la transcription et l'analyse alimentées par l'IA. Parfait pour capturer des idées en déplacement.",
"maerchenzauber_desc": "Histoires magiques du soir",
"maerchenzauber_long_desc": "Créez des histoires du soir personnalisées pour vos enfants avec l'IA. Éveillez l'imagination et rendez chaque nuit magique avec des récits uniques.",
"manadeck_desc": "Flashcards avec IA",
"manadeck_long_desc": "Créez et apprenez avec des flashcards intelligentes et une répétition espacée alimentée par l'IA.",
"picture_desc": "Génération d'images avec IA",
"picture_long_desc": "Créez des images époustouflantes avec l'IA. Transformez vos idées en œuvres d'art visuelles en quelques secondes.",
"moodlit_desc": "Ton compagnon d'humeur",
"moodlit_long_desc": "Suivez et comprenez vos émotions grâce aux analyses alimentées par l'IA. Développez votre conscience émotionnelle et améliorez votre bien-être mental.",
"manacore_desc": "Suite de productivité IA",
"manacore_long_desc": "Le hub central pour toutes les apps Manacore. Gérez vos abonnements, synchronisez vos données et accédez à de puissants outils IA depuis un seul endroit.",
"coming_soon": "Bientôt disponible",
"download": "Télécharger",
"status_published": "Publié",
"status_beta": "Bêta",
"status_development": "En développement",
"status_planning": "Planifié"
}
}

View file

@ -0,0 +1,23 @@
{
"app_slider": {
"title": "Altre App Manacore",
"memoro_desc": "Memo vocali con IA",
"memoro_long_desc": "Trasforma la tua voce in appunti organizzati e azionabili con trascrizione e analisi basate su IA. Perfetto per catturare idee in movimento.",
"maerchenzauber_desc": "Storie magiche della buonanotte",
"maerchenzauber_long_desc": "Crea storie della buonanotte personalizzate per i tuoi bambini con l'IA. Accendi l'immaginazione e rendi ogni notte magica con racconti unici.",
"manadeck_desc": "Flashcard con IA",
"manadeck_long_desc": "Crea e studia con flashcard intelligenti e ripetizione spaziata basata su IA.",
"picture_desc": "Generazione immagini con IA",
"picture_long_desc": "Crea immagini straordinarie con l'IA. Trasforma le tue idee in opere d'arte visive in pochi secondi.",
"moodlit_desc": "Il tuo compagno d'umore",
"moodlit_long_desc": "Monitora e comprendi le tue emozioni con approfondimenti basati su IA. Sviluppa la consapevolezza emotiva e migliora il tuo benessere mentale.",
"manacore_desc": "Suite di produttività IA",
"manacore_long_desc": "L'hub centrale per tutte le app Manacore. Gestisci i tuoi abbonamenti, sincronizza i dati e accedi a potenti strumenti IA da un unico posto.",
"coming_soon": "Prossimamente",
"download": "Scarica",
"status_published": "Pubblicato",
"status_beta": "Beta",
"status_development": "In sviluppo",
"status_planning": "Pianificato"
}
}

View file

@ -35,6 +35,7 @@
"@manacore/shared-branding": "workspace:*",
"@manacore/shared-feedback-service": "workspace:*",
"@manacore/shared-feedback-ui": "workspace:*",
"@manacore/shared-i18n": "workspace:*",
"@manacore/shared-icons": "workspace:*",
"@manacore/shared-profile-ui": "workspace:*",
"@manacore/shared-subscription-ui": "workspace:*",
@ -42,7 +43,8 @@
"@manacore/shared-theme": "workspace:*",
"@manacore/shared-theme-ui": "workspace:*",
"@manacore/shared-ui": "workspace:*",
"lucide-svelte": "^0.460.0"
"lucide-svelte": "^0.460.0",
"svelte-i18n": "^4.0.1"
},
"type": "module"
}

View file

@ -0,0 +1,20 @@
<script lang="ts">
import { locale } from 'svelte-i18n';
import { LanguageSelector } from '@manacore/shared-i18n';
import { setLocale, supportedLocales } from '$lib/i18n';
import { theme } from '$lib/stores/theme';
let currentLocale = $derived($locale || 'de');
function handleLocaleChange(newLocale: string) {
setLocale(newLocale as any);
}
</script>
<LanguageSelector
{currentLocale}
{supportedLocales}
onLocaleChange={handleLocaleChange}
isDark={theme.isDark}
primaryColor="#f97316"
/>

View file

@ -0,0 +1,52 @@
import { browser } from '$app/environment';
import { init, register, locale, waitLocale } from 'svelte-i18n';
// List of supported locales
export const supportedLocales = ['de', 'en', 'it', 'fr', 'es'] as const;
export type SupportedLocale = (typeof supportedLocales)[number];
// Default locale
const defaultLocale = 'de';
// Register all available locales
register('de', () => import('./locales/de.json'));
register('en', () => import('./locales/en.json'));
register('it', () => import('./locales/it.json'));
register('fr', () => import('./locales/fr.json'));
register('es', () => import('./locales/es.json'));
// Get initial locale from browser or localStorage
function getInitialLocale(): SupportedLocale {
if (browser) {
// Check localStorage first
const stored = localStorage.getItem('presi_locale');
if (stored && supportedLocales.includes(stored as SupportedLocale)) {
return stored as SupportedLocale;
}
// Fall back to browser language
const browserLang = navigator.language.split('-')[0];
if (supportedLocales.includes(browserLang as SupportedLocale)) {
return browserLang as SupportedLocale;
}
}
return defaultLocale;
}
// Initialize i18n at module scope (required for SSR)
init({
fallbackLocale: defaultLocale,
initialLocale: getInitialLocale(),
});
// Set locale and persist to localStorage
export function setLocale(newLocale: SupportedLocale) {
locale.set(newLocale);
if (browser) {
localStorage.setItem('presi_locale', newLocale);
}
}
// Wait for locale to be loaded (useful for SSR)
export { waitLocale };

View file

@ -0,0 +1,13 @@
{
"app_slider": {
"title": "Weitere Manacore Apps",
"memoro_desc": "KI-gestützte Sprachmemos",
"maerchenzauber_desc": "Magische Gute-Nacht-Geschichten",
"manadeck_desc": "KI Lernkarten",
"picture_desc": "KI Bildgenerierung",
"moodlit_desc": "Dein Stimmungsbegleiter",
"manacore_desc": "KI-Produktivitätssuite",
"coming_soon": "Demnächst",
"download": "Download"
}
}

View file

@ -0,0 +1,13 @@
{
"app_slider": {
"title": "More Manacore Apps",
"memoro_desc": "AI-powered voice memos",
"maerchenzauber_desc": "Magical bedtime stories",
"manadeck_desc": "AI flashcards",
"picture_desc": "AI image generation",
"moodlit_desc": "Your mood companion",
"manacore_desc": "AI productivity suite",
"coming_soon": "Coming soon",
"download": "Download"
}
}

View file

@ -0,0 +1,13 @@
{
"app_slider": {
"title": "Más Apps de Manacore",
"memoro_desc": "Notas de voz con IA",
"maerchenzauber_desc": "Cuentos mágicos para dormir",
"manadeck_desc": "Flashcards con IA",
"picture_desc": "Generación de imágenes con IA",
"moodlit_desc": "Tu compañero de ánimo",
"manacore_desc": "Suite de productividad IA",
"coming_soon": "Próximamente",
"download": "Descargar"
}
}

View file

@ -0,0 +1,13 @@
{
"app_slider": {
"title": "Autres Apps Manacore",
"memoro_desc": "Mémos vocaux avec IA",
"maerchenzauber_desc": "Histoires magiques du soir",
"manadeck_desc": "Flashcards avec IA",
"picture_desc": "Génération d'images avec IA",
"moodlit_desc": "Ton compagnon d'humeur",
"manacore_desc": "Suite de productivité IA",
"coming_soon": "Bientôt disponible",
"download": "Télécharger"
}
}

View file

@ -0,0 +1,13 @@
{
"app_slider": {
"title": "Altre App Manacore",
"memoro_desc": "Memo vocali con IA",
"maerchenzauber_desc": "Storie magiche della buonanotte",
"manadeck_desc": "Flashcard con IA",
"picture_desc": "Generazione immagini con IA",
"moodlit_desc": "Il tuo compagno d'umore",
"manacore_desc": "Suite di produttività IA",
"coming_soon": "Prossimamente",
"download": "Scarica"
}
}

View file

@ -1,24 +1,16 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { locale } from 'svelte-i18n';
import { ForgotPasswordPage } from '@manacore/shared-auth-ui';
import { getForgotPasswordTranslations } from '@manacore/shared-i18n';
import { PresiLogo } from '@manacore/shared-branding';
import { auth } from '$lib/stores/auth.svelte';
import AppSlider from '$lib/components/AppSlider.svelte';
import LanguageSelector from '$lib/components/LanguageSelector.svelte';
import '$lib/i18n';
// English translations
const translations = {
titleForm: 'Reset Password',
titleSuccess: 'Email Sent',
description: "Enter your email address and we'll send you a link to reset your password.",
emailPlaceholder: 'Email',
sendResetLinkButton: 'Send Reset Link',
sending: 'Sending...',
backToLogin: 'Back to Login',
resendEmail: 'Resend Email',
successMessage: "We've sent a password reset link to {email}. Please check your inbox.",
emailRequired: 'Email is required',
sendFailed: 'Failed to send reset email',
};
// Get translations based on current locale
const translations = $derived(getForgotPasswordTranslations($locale || 'de'));
async function handleForgotPassword(email: string) {
return auth.forgotPassword(email);
@ -26,7 +18,7 @@
</script>
<svelte:head>
<title>Forgot Password | Presi</title>
<title>{translations.titleForm} | Presi</title>
</svelte:head>
<ForgotPasswordPage
@ -40,6 +32,9 @@
darkBackground="#1c1210"
{translations}
>
{#snippet headerControls()}
<LanguageSelector />
{/snippet}
{#snippet appSlider()}
<AppSlider />
{/snippet}

View file

@ -1,39 +1,20 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { locale } from 'svelte-i18n';
import { LoginPage } from '@manacore/shared-auth-ui';
import { getLoginTranslations } from '@manacore/shared-i18n';
import { PresiLogo } from '@manacore/shared-branding';
import { auth } from '$lib/stores/auth.svelte';
import AppSlider from '$lib/components/AppSlider.svelte';
import LanguageSelector from '$lib/components/LanguageSelector.svelte';
import '$lib/i18n';
// Get redirect URL from query params
const redirectTo = $derived($page.url.searchParams.get('redirectTo') || '/');
// English translations
const translations = {
title: 'Sign In',
subtitle: 'Sign in with your account',
emailPlaceholder: 'Email',
passwordPlaceholder: 'Password',
rememberMe: 'Remember me',
forgotPassword: 'Forgot password?',
signInButton: 'Sign In',
signingIn: 'Signing in...',
success: 'Success!',
orDivider: 'or',
noAccount: "Don't have an account?",
createAccount: 'Create one',
skipToForm: 'Skip to login form',
showPassword: 'Show password',
hidePassword: 'Hide password',
emailRequired: 'Email is required',
emailInvalid: 'Please enter a valid email address',
passwordRequired: 'Password is required',
signInFailed: 'Sign in failed',
googleSignInFailed: 'Google sign in failed',
signInSuccess: 'Successfully signed in. Redirecting...',
googleSignInSuccess: 'Successfully signed in with Google. Redirecting...',
};
// Get translations based on current locale
const translations = $derived(getLoginTranslations($locale || 'de'));
async function handleSignIn(email: string, password: string) {
return auth.login(email, password);
@ -41,7 +22,7 @@
</script>
<svelte:head>
<title>Login | Presi</title>
<title>{translations.title} | Presi</title>
</svelte:head>
<LoginPage
@ -59,6 +40,9 @@
darkBackground="#1c1210"
{translations}
>
{#snippet headerControls()}
<LanguageSelector />
{/snippet}
{#snippet appSlider()}
<AppSlider />
{/snippet}

View file

@ -1,33 +1,16 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { locale } from 'svelte-i18n';
import { RegisterPage } from '@manacore/shared-auth-ui';
import { getRegisterTranslations } from '@manacore/shared-i18n';
import { PresiLogo } from '@manacore/shared-branding';
import { auth } from '$lib/stores/auth.svelte';
import AppSlider from '$lib/components/AppSlider.svelte';
import LanguageSelector from '$lib/components/LanguageSelector.svelte';
import '$lib/i18n';
// English translations
const translations = {
title: 'Create Account',
emailPlaceholder: 'Email',
passwordPlaceholder: 'Password',
confirmPasswordPlaceholder: 'Confirm Password',
passwordRequirements:
'Password must be at least 8 characters with lowercase, uppercase, number, and special character.',
createAccountButton: 'Create Account',
creatingAccount: 'Creating Account...',
backToLogin: 'Back to Login',
showPassword: 'Show password',
hidePassword: 'Hide password',
emailRequired: 'Email is required',
passwordRequired: 'Password is required',
confirmPasswordRequired: 'Please confirm your password',
passwordsDoNotMatch: 'Passwords do not match',
passwordTooShort: 'Password must be at least 8 characters',
passwordStrengthError:
'Password must include lowercase, uppercase, number, and special character',
registrationFailed: 'Registration failed',
accountCreated: 'Account created! Please check your email to verify your account.',
};
// Get translations based on current locale
const translations = $derived(getRegisterTranslations($locale || 'de'));
async function handleSignUp(email: string, password: string) {
return auth.register(email, password);
@ -35,7 +18,7 @@
</script>
<svelte:head>
<title>Register | Presi</title>
<title>{translations.title} | Presi</title>
</svelte:head>
<RegisterPage
@ -50,6 +33,9 @@
darkBackground="#1c1210"
{translations}
>
{#snippet headerControls()}
<LanguageSelector />
{/snippet}
{#snippet appSlider()}
<AppSlider />
{/snippet}

View file

@ -32,6 +32,7 @@
"@manacore/shared-branding": "workspace:*",
"@manacore/shared-feedback-service": "workspace:*",
"@manacore/shared-feedback-ui": "workspace:*",
"@manacore/shared-i18n": "workspace:*",
"@manacore/shared-icons": "workspace:*",
"@manacore/shared-profile-ui": "workspace:*",
"@manacore/shared-subscription-ui": "workspace:*",
@ -40,7 +41,8 @@
"@manacore/shared-theme-ui": "workspace:*",
"@manacore/shared-ui": "workspace:*",
"@zitare/shared": "workspace:*",
"@zitare/web-ui": "workspace:*"
"@zitare/web-ui": "workspace:*",
"svelte-i18n": "^4.0.1"
},
"type": "module"
}

View file

@ -0,0 +1,20 @@
<script lang="ts">
import { locale } from 'svelte-i18n';
import { LanguageSelector } from '@manacore/shared-i18n';
import { setLocale, supportedLocales } from '$lib/i18n';
import { theme } from '$lib/stores/theme';
let currentLocale = $derived($locale || 'de');
function handleLocaleChange(newLocale: string) {
setLocale(newLocale as any);
}
</script>
<LanguageSelector
{currentLocale}
{supportedLocales}
onLocaleChange={handleLocaleChange}
isDark={theme.isDark}
primaryColor="#f59e0b"
/>

View file

@ -0,0 +1,52 @@
import { browser } from '$app/environment';
import { init, register, locale, waitLocale } from 'svelte-i18n';
// List of supported locales
export const supportedLocales = ['de', 'en', 'it', 'fr', 'es'] as const;
export type SupportedLocale = (typeof supportedLocales)[number];
// Default locale
const defaultLocale = 'de';
// Register all available locales
register('de', () => import('./locales/de.json'));
register('en', () => import('./locales/en.json'));
register('it', () => import('./locales/it.json'));
register('fr', () => import('./locales/fr.json'));
register('es', () => import('./locales/es.json'));
// Get initial locale from browser or localStorage
function getInitialLocale(): SupportedLocale {
if (browser) {
// Check localStorage first
const stored = localStorage.getItem('zitare_locale');
if (stored && supportedLocales.includes(stored as SupportedLocale)) {
return stored as SupportedLocale;
}
// Fall back to browser language
const browserLang = navigator.language.split('-')[0];
if (supportedLocales.includes(browserLang as SupportedLocale)) {
return browserLang as SupportedLocale;
}
}
return defaultLocale;
}
// Initialize i18n at module scope (required for SSR)
init({
fallbackLocale: defaultLocale,
initialLocale: getInitialLocale(),
});
// Set locale and persist to localStorage
export function setLocale(newLocale: SupportedLocale) {
locale.set(newLocale);
if (browser) {
localStorage.setItem('zitare_locale', newLocale);
}
}
// Wait for locale to be loaded (useful for SSR)
export { waitLocale };

View file

@ -0,0 +1,13 @@
{
"app_slider": {
"title": "Weitere Manacore Apps",
"memoro_desc": "KI-gestützte Sprachmemos",
"maerchenzauber_desc": "Magische Gute-Nacht-Geschichten",
"manadeck_desc": "KI Lernkarten",
"picture_desc": "KI Bildgenerierung",
"moodlit_desc": "Dein Stimmungsbegleiter",
"manacore_desc": "KI-Produktivitätssuite",
"coming_soon": "Demnächst",
"download": "Download"
}
}

View file

@ -0,0 +1,13 @@
{
"app_slider": {
"title": "More Manacore Apps",
"memoro_desc": "AI-powered voice memos",
"maerchenzauber_desc": "Magical bedtime stories",
"manadeck_desc": "AI flashcards",
"picture_desc": "AI image generation",
"moodlit_desc": "Your mood companion",
"manacore_desc": "AI productivity suite",
"coming_soon": "Coming soon",
"download": "Download"
}
}

View file

@ -0,0 +1,13 @@
{
"app_slider": {
"title": "Más Apps de Manacore",
"memoro_desc": "Notas de voz con IA",
"maerchenzauber_desc": "Cuentos mágicos para dormir",
"manadeck_desc": "Flashcards con IA",
"picture_desc": "Generación de imágenes con IA",
"moodlit_desc": "Tu compañero de ánimo",
"manacore_desc": "Suite de productividad IA",
"coming_soon": "Próximamente",
"download": "Descargar"
}
}

View file

@ -0,0 +1,13 @@
{
"app_slider": {
"title": "Autres Apps Manacore",
"memoro_desc": "Mémos vocaux avec IA",
"maerchenzauber_desc": "Histoires magiques du soir",
"manadeck_desc": "Flashcards avec IA",
"picture_desc": "Génération d'images avec IA",
"moodlit_desc": "Ton compagnon d'humeur",
"manacore_desc": "Suite de productivité IA",
"coming_soon": "Bientôt disponible",
"download": "Télécharger"
}
}

View file

@ -0,0 +1,13 @@
{
"app_slider": {
"title": "Altre App Manacore",
"memoro_desc": "Memo vocali con IA",
"maerchenzauber_desc": "Storie magiche della buonanotte",
"manadeck_desc": "Flashcard con IA",
"picture_desc": "Generazione immagini con IA",
"moodlit_desc": "Il tuo compagno d'umore",
"manacore_desc": "Suite di produttività IA",
"coming_soon": "Prossimamente",
"download": "Scarica"
}
}

View file

@ -1,8 +1,14 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { locale } from 'svelte-i18n';
import { ForgotPasswordPage } from '@manacore/shared-auth-ui';
import { ZitareLogo } from '@manacore/shared-branding';
import { getForgotPasswordTranslations } from '@manacore/shared-i18n';
import { authStore } from '$lib/stores/auth.svelte';
import LanguageSelector from '$lib/components/LanguageSelector.svelte';
import '$lib/i18n';
const translations = $derived(getForgotPasswordTranslations($locale || 'de'));
async function handleForgotPassword(email: string) {
return authStore.resetPassword(email);
@ -10,7 +16,7 @@
</script>
<svelte:head>
<title>Passwort vergessen - Zitare</title>
<title>{translations.titleForm} - Zitare</title>
</svelte:head>
<ForgotPasswordPage
@ -22,4 +28,9 @@
loginPath="/login"
lightBackground="#fffbeb"
darkBackground="#1c1917"
/>
{translations}
>
{#snippet headerControls()}
<LanguageSelector />
{/snippet}
</ForgotPasswordPage>

View file

@ -1,8 +1,14 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { locale } from 'svelte-i18n';
import { LoginPage } from '@manacore/shared-auth-ui';
import { ZitareLogo } from '@manacore/shared-branding';
import { getLoginTranslations } from '@manacore/shared-i18n';
import { authStore } from '$lib/stores/auth.svelte';
import LanguageSelector from '$lib/components/LanguageSelector.svelte';
import '$lib/i18n';
const translations = $derived(getLoginTranslations($locale || 'de'));
async function handleSignIn(email: string, password: string) {
return authStore.signIn(email, password);
@ -10,7 +16,7 @@
</script>
<svelte:head>
<title>Anmelden - Zitare</title>
<title>{translations.title} - Zitare</title>
</svelte:head>
<LoginPage
@ -24,4 +30,9 @@
forgotPasswordPath="/forgot-password"
lightBackground="#fffbeb"
darkBackground="#1c1917"
/>
{translations}
>
{#snippet headerControls()}
<LanguageSelector />
{/snippet}
</LoginPage>

View file

@ -1,8 +1,14 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { locale } from 'svelte-i18n';
import { RegisterPage } from '@manacore/shared-auth-ui';
import { ZitareLogo } from '@manacore/shared-branding';
import { getRegisterTranslations } from '@manacore/shared-i18n';
import { authStore } from '$lib/stores/auth.svelte';
import LanguageSelector from '$lib/components/LanguageSelector.svelte';
import '$lib/i18n';
const translations = $derived(getRegisterTranslations($locale || 'de'));
async function handleSignUp(email: string, password: string) {
return authStore.signUp(email, password);
@ -10,7 +16,7 @@
</script>
<svelte:head>
<title>Registrieren - Zitare</title>
<title>{translations.title} - Zitare</title>
</svelte:head>
<RegisterPage
@ -23,4 +29,9 @@
loginPath="/login"
lightBackground="#fffbeb"
darkBackground="#1c1917"
/>
{translations}
>
{#snippet headerControls()}
<LanguageSelector />
{/snippet}
</RegisterPage>

View file

@ -56,6 +56,8 @@
darkBackground?: string;
/** App slider snippet */
appSlider?: Snippet;
/** Header controls snippet (e.g., language selector) */
headerControls?: Snippet;
/** Translations for i18n support */
translations?: Partial<ForgotPasswordTranslations>;
}
@ -70,6 +72,7 @@
lightBackground = '#f5f5f5',
darkBackground = '#121212',
appSlider,
headerControls,
translations = {},
}: Props = $props();
@ -138,6 +141,12 @@
class="flex min-h-screen flex-col justify-between"
style="background-color: {getPageBackground()}; max-width: 100vw; overflow-x: hidden;"
>
{#if headerControls}
<div style="position: absolute; top: 1rem; right: 1rem; z-index: 50; opacity: 0.6; display: flex; gap: 0.75rem;">
{@render headerControls()}
</div>
{/if}
<!-- Top Section - Logo -->
<div class="flex flex-col items-center justify-center pt-16 pb-8">
<div

View file

@ -74,6 +74,8 @@
darkBackground?: string;
/** App slider snippet */
appSlider?: Snippet;
/** Header controls snippet (e.g., language selector) */
headerControls?: Snippet;
/** Translations for i18n support */
translations?: Partial<RegisterTranslations>;
}
@ -89,6 +91,7 @@
lightBackground = '#f5f5f5',
darkBackground = '#121212',
appSlider,
headerControls,
translations = {},
}: Props = $props();
@ -220,6 +223,12 @@
class="flex min-h-screen flex-col justify-between"
style="background-color: {getPageBackground()}; max-width: 100vw; overflow-x: hidden;"
>
{#if headerControls}
<div style="position: absolute; top: 1rem; right: 1rem; z-index: 50; opacity: 0.6; display: flex; gap: 0.75rem;">
{@render headerControls()}
</div>
{/if}
<!-- Top Section - Logo -->
<div class="flex flex-col items-center justify-center pt-16 pb-8">
<div

View file

@ -22,6 +22,8 @@ export {
UloadLogo,
ChatLogo,
PresiLogo,
NutriPhiLogo,
ZitareLogo,
} from './logos';
// Configuration

15
pnpm-lock.yaml generated
View file

@ -313,6 +313,9 @@ importers:
marked:
specifier: ^17.0.0
version: 17.0.1
svelte-i18n:
specifier: ^4.0.1
version: 4.0.1(svelte@5.44.0)
devDependencies:
'@sveltejs/adapter-auto':
specifier: ^6.0.0
@ -1776,6 +1779,9 @@ importers:
'@manacore/shared-feedback-ui':
specifier: workspace:*
version: link:../../../../packages/shared-feedback-ui
'@manacore/shared-i18n':
specifier: workspace:*
version: link:../../../../packages/shared-i18n
'@manacore/shared-icons':
specifier: workspace:*
version: link:../../../../packages/shared-icons
@ -1803,6 +1809,9 @@ importers:
lucide-svelte:
specifier: ^0.460.0
version: 0.460.1(svelte@5.44.0)
svelte-i18n:
specifier: ^4.0.1
version: 4.0.1(svelte@5.44.0)
devDependencies:
'@sveltejs/adapter-auto':
specifier: ^3.0.0
@ -2164,6 +2173,9 @@ importers:
'@manacore/shared-feedback-ui':
specifier: workspace:*
version: link:../../../../packages/shared-feedback-ui
'@manacore/shared-i18n':
specifier: workspace:*
version: link:../../../../packages/shared-i18n
'@manacore/shared-icons':
specifier: workspace:*
version: link:../../../../packages/shared-icons
@ -2191,6 +2203,9 @@ importers:
'@zitare/web-ui':
specifier: workspace:*
version: link:../../packages/web-ui
svelte-i18n:
specifier: ^4.0.1
version: 4.0.1(svelte@5.44.0)
devDependencies:
'@sveltejs/adapter-auto':
specifier: ^3.0.0