mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-21 20:26:42 +02:00
🌐 feat: add i18n support to 6 web apps
Add internationalization (DE + EN) to previously missing apps:
- todo: task management translations
- skilltree: skill/XP system translations
- nutriphi: nutrition tracking translations
- planta: plant care translations
- questions: research app translations
- matrix: chat client translations (layout integration)
Each app includes:
- svelte-i18n setup with SSR support
- localStorage persistence ({app}_locale pattern)
- i18n loading state in +layout.svelte
- German (default) and English translations
Updated CONSISTENCY_REPORT.md to mark i18n task as complete.
Also includes:
- mana-tts service placeholder files
This commit is contained in:
parent
a938ed86d4
commit
5a0815708c
35 changed files with 3440 additions and 56 deletions
|
|
@ -1,7 +1,9 @@
|
|||
<script lang="ts">
|
||||
import '../app.css';
|
||||
import '$lib/i18n';
|
||||
import { onMount } from 'svelte';
|
||||
import type { Snippet } from 'svelte';
|
||||
import { isLoading as i18nLoading, _ as t } from 'svelte-i18n';
|
||||
import { theme } from '$lib/stores/theme';
|
||||
import { ToastContainer } from '@manacore/shared-ui';
|
||||
|
||||
|
|
@ -18,7 +20,7 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Mana Matrix</title>
|
||||
<title>{$t('app.name')}</title>
|
||||
<meta name="description" content="Self-hosted Matrix chat client" />
|
||||
</svelte:head>
|
||||
|
||||
|
|
|
|||
49
apps/nutriphi/apps/web/src/lib/i18n/index.ts
Normal file
49
apps/nutriphi/apps/web/src/lib/i18n/index.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
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 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'));
|
||||
|
||||
// Get initial locale from browser or localStorage
|
||||
function getInitialLocale(): SupportedLocale {
|
||||
if (browser) {
|
||||
// Check localStorage first
|
||||
const stored = localStorage.getItem('nutriphi_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('nutriphi_locale', newLocale);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for locale to be loaded (useful for SSR)
|
||||
export { waitLocale };
|
||||
89
apps/nutriphi/apps/web/src/lib/i18n/locales/de.json
Normal file
89
apps/nutriphi/apps/web/src/lib/i18n/locales/de.json
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
{
|
||||
"app": {
|
||||
"name": "NutriPhi",
|
||||
"loading": "Laden...",
|
||||
"tagline": "Ernährung verstehen"
|
||||
},
|
||||
"nav": {
|
||||
"dashboard": "Dashboard",
|
||||
"meals": "Mahlzeiten",
|
||||
"goals": "Ziele",
|
||||
"favorites": "Favoriten",
|
||||
"stats": "Statistiken",
|
||||
"settings": "Einstellungen"
|
||||
},
|
||||
"meal": {
|
||||
"add": "Mahlzeit hinzufügen",
|
||||
"edit": "Mahlzeit bearbeiten",
|
||||
"delete": "Mahlzeit löschen",
|
||||
"photo": "Foto aufnehmen",
|
||||
"text": "Beschreiben",
|
||||
"analyzing": "Analysiere...",
|
||||
"noMeals": "Noch keine Mahlzeiten",
|
||||
"breakfast": "Frühstück",
|
||||
"lunch": "Mittagessen",
|
||||
"dinner": "Abendessen",
|
||||
"snack": "Snack"
|
||||
},
|
||||
"nutrition": {
|
||||
"calories": "Kalorien",
|
||||
"protein": "Protein",
|
||||
"carbs": "Kohlenhydrate",
|
||||
"fat": "Fett",
|
||||
"fiber": "Ballaststoffe",
|
||||
"sugar": "Zucker",
|
||||
"kcal": "kcal",
|
||||
"grams": "g"
|
||||
},
|
||||
"goals": {
|
||||
"daily": "Tagesziele",
|
||||
"setGoals": "Ziele setzen",
|
||||
"calories": "Kalorien-Ziel",
|
||||
"protein": "Protein-Ziel",
|
||||
"carbs": "Kohlenhydrate-Ziel",
|
||||
"fat": "Fett-Ziel",
|
||||
"progress": "Fortschritt"
|
||||
},
|
||||
"stats": {
|
||||
"today": "Heute",
|
||||
"week": "Diese Woche",
|
||||
"remaining": "Verbleibend",
|
||||
"consumed": "Verzehrt",
|
||||
"average": "Durchschnitt"
|
||||
},
|
||||
"favorites": {
|
||||
"add": "Zu Favoriten",
|
||||
"remove": "Aus Favoriten entfernen",
|
||||
"noFavorites": "Keine Favoriten",
|
||||
"useAgain": "Erneut verwenden"
|
||||
},
|
||||
"auth": {
|
||||
"login": "Anmelden",
|
||||
"logout": "Abmelden",
|
||||
"register": "Registrieren"
|
||||
},
|
||||
"common": {
|
||||
"save": "Speichern",
|
||||
"cancel": "Abbrechen",
|
||||
"delete": "Löschen",
|
||||
"edit": "Bearbeiten",
|
||||
"add": "Hinzufügen",
|
||||
"close": "Schließen",
|
||||
"search": "Suchen",
|
||||
"error": "Fehler",
|
||||
"success": "Erfolgreich",
|
||||
"loading": "Laden..."
|
||||
},
|
||||
"errors": {
|
||||
"loadMeals": "Mahlzeiten konnten nicht geladen werden",
|
||||
"analyzeFailed": "Analyse fehlgeschlagen",
|
||||
"saveFailed": "Speichern fehlgeschlagen",
|
||||
"loadGoals": "Ziele konnten nicht geladen werden"
|
||||
},
|
||||
"success": {
|
||||
"mealAdded": "Mahlzeit hinzugefügt",
|
||||
"mealDeleted": "Mahlzeit gelöscht",
|
||||
"goalsSaved": "Ziele gespeichert",
|
||||
"favoriteAdded": "Zu Favoriten hinzugefügt"
|
||||
}
|
||||
}
|
||||
89
apps/nutriphi/apps/web/src/lib/i18n/locales/en.json
Normal file
89
apps/nutriphi/apps/web/src/lib/i18n/locales/en.json
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
{
|
||||
"app": {
|
||||
"name": "NutriPhi",
|
||||
"loading": "Loading...",
|
||||
"tagline": "Understand nutrition"
|
||||
},
|
||||
"nav": {
|
||||
"dashboard": "Dashboard",
|
||||
"meals": "Meals",
|
||||
"goals": "Goals",
|
||||
"favorites": "Favorites",
|
||||
"stats": "Statistics",
|
||||
"settings": "Settings"
|
||||
},
|
||||
"meal": {
|
||||
"add": "Add meal",
|
||||
"edit": "Edit meal",
|
||||
"delete": "Delete meal",
|
||||
"photo": "Take photo",
|
||||
"text": "Describe",
|
||||
"analyzing": "Analyzing...",
|
||||
"noMeals": "No meals yet",
|
||||
"breakfast": "Breakfast",
|
||||
"lunch": "Lunch",
|
||||
"dinner": "Dinner",
|
||||
"snack": "Snack"
|
||||
},
|
||||
"nutrition": {
|
||||
"calories": "Calories",
|
||||
"protein": "Protein",
|
||||
"carbs": "Carbohydrates",
|
||||
"fat": "Fat",
|
||||
"fiber": "Fiber",
|
||||
"sugar": "Sugar",
|
||||
"kcal": "kcal",
|
||||
"grams": "g"
|
||||
},
|
||||
"goals": {
|
||||
"daily": "Daily goals",
|
||||
"setGoals": "Set goals",
|
||||
"calories": "Calorie goal",
|
||||
"protein": "Protein goal",
|
||||
"carbs": "Carbohydrate goal",
|
||||
"fat": "Fat goal",
|
||||
"progress": "Progress"
|
||||
},
|
||||
"stats": {
|
||||
"today": "Today",
|
||||
"week": "This week",
|
||||
"remaining": "Remaining",
|
||||
"consumed": "Consumed",
|
||||
"average": "Average"
|
||||
},
|
||||
"favorites": {
|
||||
"add": "Add to favorites",
|
||||
"remove": "Remove from favorites",
|
||||
"noFavorites": "No favorites",
|
||||
"useAgain": "Use again"
|
||||
},
|
||||
"auth": {
|
||||
"login": "Login",
|
||||
"logout": "Logout",
|
||||
"register": "Register"
|
||||
},
|
||||
"common": {
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"delete": "Delete",
|
||||
"edit": "Edit",
|
||||
"add": "Add",
|
||||
"close": "Close",
|
||||
"search": "Search",
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
"loading": "Loading..."
|
||||
},
|
||||
"errors": {
|
||||
"loadMeals": "Failed to load meals",
|
||||
"analyzeFailed": "Analysis failed",
|
||||
"saveFailed": "Failed to save",
|
||||
"loadGoals": "Failed to load goals"
|
||||
},
|
||||
"success": {
|
||||
"mealAdded": "Meal added",
|
||||
"mealDeleted": "Meal deleted",
|
||||
"goalsSaved": "Goals saved",
|
||||
"favoriteAdded": "Added to favorites"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
<script lang="ts">
|
||||
import '../app.css';
|
||||
import '$lib/i18n';
|
||||
import { isLoading as i18nLoading, _ as t } from 'svelte-i18n';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
|
||||
let { children } = $props();
|
||||
|
|
@ -11,7 +13,13 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>NutriPhi - Ernährung verstehen</title>
|
||||
<title>{$t('app.name')} - {$t('app.tagline')}</title>
|
||||
</svelte:head>
|
||||
|
||||
{@render children()}
|
||||
{#if $i18nLoading}
|
||||
<div class="flex min-h-screen items-center justify-center">
|
||||
<p>{$t('app.loading')}</p>
|
||||
</div>
|
||||
{:else}
|
||||
{@render children()}
|
||||
{/if}
|
||||
|
|
|
|||
49
apps/planta/apps/web/src/lib/i18n/index.ts
Normal file
49
apps/planta/apps/web/src/lib/i18n/index.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
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 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'));
|
||||
|
||||
// Get initial locale from browser or localStorage
|
||||
function getInitialLocale(): SupportedLocale {
|
||||
if (browser) {
|
||||
// Check localStorage first
|
||||
const stored = localStorage.getItem('planta_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('planta_locale', newLocale);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for locale to be loaded (useful for SSR)
|
||||
export { waitLocale };
|
||||
87
apps/planta/apps/web/src/lib/i18n/locales/de.json
Normal file
87
apps/planta/apps/web/src/lib/i18n/locales/de.json
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
{
|
||||
"app": {
|
||||
"name": "Planta",
|
||||
"loading": "Laden...",
|
||||
"tagline": "Pflanzenpflege leicht gemacht"
|
||||
},
|
||||
"nav": {
|
||||
"plants": "Pflanzen",
|
||||
"watering": "Gießen",
|
||||
"identify": "Identifizieren",
|
||||
"settings": "Einstellungen"
|
||||
},
|
||||
"plant": {
|
||||
"add": "Pflanze hinzufügen",
|
||||
"edit": "Pflanze bearbeiten",
|
||||
"delete": "Pflanze löschen",
|
||||
"name": "Name",
|
||||
"species": "Art",
|
||||
"location": "Standort",
|
||||
"noPlants": "Noch keine Pflanzen",
|
||||
"addFirst": "Füge deine erste Pflanze hinzu",
|
||||
"careNotes": "Pflegehinweise",
|
||||
"health": "Gesundheit"
|
||||
},
|
||||
"health": {
|
||||
"healthy": "Gesund",
|
||||
"needsAttention": "Braucht Aufmerksamkeit",
|
||||
"sick": "Krank"
|
||||
},
|
||||
"watering": {
|
||||
"water": "Gießen",
|
||||
"watered": "Gegossen",
|
||||
"lastWatered": "Zuletzt gegossen",
|
||||
"nextWatering": "Nächstes Gießen",
|
||||
"daysUntil": "in {days} Tagen",
|
||||
"overdue": "Überfällig",
|
||||
"today": "Heute gießen",
|
||||
"noWatering": "Keine Pflanzen zum Gießen"
|
||||
},
|
||||
"identify": {
|
||||
"takePhoto": "Foto aufnehmen",
|
||||
"analyzing": "Analysiere...",
|
||||
"identified": "Identifiziert",
|
||||
"confidence": "Sicherheit",
|
||||
"tips": "Pflegetipps"
|
||||
},
|
||||
"light": {
|
||||
"low": "Wenig Licht",
|
||||
"medium": "Mittleres Licht",
|
||||
"bright": "Helles Licht",
|
||||
"direct": "Direktes Sonnenlicht"
|
||||
},
|
||||
"humidity": {
|
||||
"low": "Niedrig",
|
||||
"medium": "Mittel",
|
||||
"high": "Hoch"
|
||||
},
|
||||
"auth": {
|
||||
"login": "Anmelden",
|
||||
"logout": "Abmelden",
|
||||
"register": "Registrieren"
|
||||
},
|
||||
"common": {
|
||||
"save": "Speichern",
|
||||
"cancel": "Abbrechen",
|
||||
"delete": "Löschen",
|
||||
"edit": "Bearbeiten",
|
||||
"add": "Hinzufügen",
|
||||
"close": "Schließen",
|
||||
"search": "Suchen",
|
||||
"error": "Fehler",
|
||||
"success": "Erfolgreich",
|
||||
"loading": "Laden..."
|
||||
},
|
||||
"errors": {
|
||||
"loadPlants": "Pflanzen konnten nicht geladen werden",
|
||||
"identifyFailed": "Identifizierung fehlgeschlagen",
|
||||
"saveFailed": "Speichern fehlgeschlagen",
|
||||
"uploadFailed": "Upload fehlgeschlagen"
|
||||
},
|
||||
"success": {
|
||||
"plantAdded": "Pflanze hinzugefügt",
|
||||
"plantDeleted": "Pflanze gelöscht",
|
||||
"plantWatered": "Pflanze gegossen",
|
||||
"photoUploaded": "Foto hochgeladen"
|
||||
}
|
||||
}
|
||||
87
apps/planta/apps/web/src/lib/i18n/locales/en.json
Normal file
87
apps/planta/apps/web/src/lib/i18n/locales/en.json
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
{
|
||||
"app": {
|
||||
"name": "Planta",
|
||||
"loading": "Loading...",
|
||||
"tagline": "Plant care made easy"
|
||||
},
|
||||
"nav": {
|
||||
"plants": "Plants",
|
||||
"watering": "Watering",
|
||||
"identify": "Identify",
|
||||
"settings": "Settings"
|
||||
},
|
||||
"plant": {
|
||||
"add": "Add plant",
|
||||
"edit": "Edit plant",
|
||||
"delete": "Delete plant",
|
||||
"name": "Name",
|
||||
"species": "Species",
|
||||
"location": "Location",
|
||||
"noPlants": "No plants yet",
|
||||
"addFirst": "Add your first plant",
|
||||
"careNotes": "Care notes",
|
||||
"health": "Health"
|
||||
},
|
||||
"health": {
|
||||
"healthy": "Healthy",
|
||||
"needsAttention": "Needs attention",
|
||||
"sick": "Sick"
|
||||
},
|
||||
"watering": {
|
||||
"water": "Water",
|
||||
"watered": "Watered",
|
||||
"lastWatered": "Last watered",
|
||||
"nextWatering": "Next watering",
|
||||
"daysUntil": "in {days} days",
|
||||
"overdue": "Overdue",
|
||||
"today": "Water today",
|
||||
"noWatering": "No plants to water"
|
||||
},
|
||||
"identify": {
|
||||
"takePhoto": "Take photo",
|
||||
"analyzing": "Analyzing...",
|
||||
"identified": "Identified",
|
||||
"confidence": "Confidence",
|
||||
"tips": "Care tips"
|
||||
},
|
||||
"light": {
|
||||
"low": "Low light",
|
||||
"medium": "Medium light",
|
||||
"bright": "Bright light",
|
||||
"direct": "Direct sunlight"
|
||||
},
|
||||
"humidity": {
|
||||
"low": "Low",
|
||||
"medium": "Medium",
|
||||
"high": "High"
|
||||
},
|
||||
"auth": {
|
||||
"login": "Login",
|
||||
"logout": "Logout",
|
||||
"register": "Register"
|
||||
},
|
||||
"common": {
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"delete": "Delete",
|
||||
"edit": "Edit",
|
||||
"add": "Add",
|
||||
"close": "Close",
|
||||
"search": "Search",
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
"loading": "Loading..."
|
||||
},
|
||||
"errors": {
|
||||
"loadPlants": "Failed to load plants",
|
||||
"identifyFailed": "Identification failed",
|
||||
"saveFailed": "Failed to save",
|
||||
"uploadFailed": "Upload failed"
|
||||
},
|
||||
"success": {
|
||||
"plantAdded": "Plant added",
|
||||
"plantDeleted": "Plant deleted",
|
||||
"plantWatered": "Plant watered",
|
||||
"photoUploaded": "Photo uploaded"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,15 @@
|
|||
<script lang="ts">
|
||||
import '../app.css';
|
||||
import '$lib/i18n';
|
||||
import { onMount } from 'svelte';
|
||||
import { isLoading as i18nLoading, _ as t } from 'svelte-i18n';
|
||||
import { theme } from '$lib/stores/theme';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
let loading = $state(true);
|
||||
let appReady = $derived(!loading && !$i18nLoading);
|
||||
|
||||
onMount(async () => {
|
||||
// Initialize theme
|
||||
|
|
@ -19,13 +22,13 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
{#if loading}
|
||||
{#if !appReady}
|
||||
<div class="flex min-h-screen items-center justify-center bg-background">
|
||||
<div class="text-center">
|
||||
<div
|
||||
class="mb-4 inline-block h-12 w-12 animate-spin rounded-full border-4 border-solid border-primary border-r-transparent"
|
||||
></div>
|
||||
<p class="text-muted-foreground">Laden...</p>
|
||||
<p class="text-muted-foreground">{$t('common.loading')}</p>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
|
|
|
|||
49
apps/questions/apps/web/src/lib/i18n/index.ts
Normal file
49
apps/questions/apps/web/src/lib/i18n/index.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
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 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'));
|
||||
|
||||
// Get initial locale from browser or localStorage
|
||||
function getInitialLocale(): SupportedLocale {
|
||||
if (browser) {
|
||||
// Check localStorage first
|
||||
const stored = localStorage.getItem('questions_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('questions_locale', newLocale);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for locale to be loaded (useful for SSR)
|
||||
export { waitLocale };
|
||||
96
apps/questions/apps/web/src/lib/i18n/locales/de.json
Normal file
96
apps/questions/apps/web/src/lib/i18n/locales/de.json
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
{
|
||||
"app": {
|
||||
"name": "Questions",
|
||||
"loading": "Laden...",
|
||||
"tagline": "KI-gestützte Recherche"
|
||||
},
|
||||
"nav": {
|
||||
"questions": "Fragen",
|
||||
"collections": "Sammlungen",
|
||||
"research": "Recherche",
|
||||
"settings": "Einstellungen"
|
||||
},
|
||||
"question": {
|
||||
"create": "Frage erstellen",
|
||||
"edit": "Frage bearbeiten",
|
||||
"delete": "Frage löschen",
|
||||
"title": "Frage",
|
||||
"description": "Beschreibung",
|
||||
"status": "Status",
|
||||
"priority": "Priorität",
|
||||
"noQuestions": "Noch keine Fragen",
|
||||
"addFirst": "Stelle deine erste Frage"
|
||||
},
|
||||
"status": {
|
||||
"open": "Offen",
|
||||
"researching": "Wird recherchiert",
|
||||
"answered": "Beantwortet",
|
||||
"archived": "Archiviert"
|
||||
},
|
||||
"priority": {
|
||||
"low": "Niedrig",
|
||||
"medium": "Mittel",
|
||||
"high": "Hoch"
|
||||
},
|
||||
"collection": {
|
||||
"create": "Sammlung erstellen",
|
||||
"edit": "Sammlung bearbeiten",
|
||||
"delete": "Sammlung löschen",
|
||||
"name": "Name",
|
||||
"color": "Farbe",
|
||||
"noCollections": "Keine Sammlungen"
|
||||
},
|
||||
"research": {
|
||||
"start": "Recherche starten",
|
||||
"inProgress": "Recherche läuft...",
|
||||
"depth": "Recherchetiefe",
|
||||
"quick": "Schnell",
|
||||
"standard": "Standard",
|
||||
"deep": "Tiefgehend",
|
||||
"sources": "Quellen",
|
||||
"summary": "Zusammenfassung",
|
||||
"keyPoints": "Kernpunkte",
|
||||
"followUp": "Weiterführende Fragen"
|
||||
},
|
||||
"answer": {
|
||||
"create": "Antwort erstellen",
|
||||
"edit": "Antwort bearbeiten",
|
||||
"accept": "Antwort akzeptieren",
|
||||
"rate": "Bewerten",
|
||||
"noAnswer": "Noch keine Antwort"
|
||||
},
|
||||
"source": {
|
||||
"view": "Quelle ansehen",
|
||||
"extract": "Inhalt extrahieren",
|
||||
"noSources": "Keine Quellen gefunden"
|
||||
},
|
||||
"auth": {
|
||||
"login": "Anmelden",
|
||||
"logout": "Abmelden",
|
||||
"register": "Registrieren"
|
||||
},
|
||||
"common": {
|
||||
"save": "Speichern",
|
||||
"cancel": "Abbrechen",
|
||||
"delete": "Löschen",
|
||||
"edit": "Bearbeiten",
|
||||
"add": "Hinzufügen",
|
||||
"close": "Schließen",
|
||||
"search": "Suchen",
|
||||
"error": "Fehler",
|
||||
"success": "Erfolgreich",
|
||||
"loading": "Laden..."
|
||||
},
|
||||
"errors": {
|
||||
"loadQuestions": "Fragen konnten nicht geladen werden",
|
||||
"researchFailed": "Recherche fehlgeschlagen",
|
||||
"saveFailed": "Speichern fehlgeschlagen",
|
||||
"loadSources": "Quellen konnten nicht geladen werden"
|
||||
},
|
||||
"success": {
|
||||
"questionCreated": "Frage erstellt",
|
||||
"questionDeleted": "Frage gelöscht",
|
||||
"researchStarted": "Recherche gestartet",
|
||||
"answerAccepted": "Antwort akzeptiert"
|
||||
}
|
||||
}
|
||||
96
apps/questions/apps/web/src/lib/i18n/locales/en.json
Normal file
96
apps/questions/apps/web/src/lib/i18n/locales/en.json
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
{
|
||||
"app": {
|
||||
"name": "Questions",
|
||||
"loading": "Loading...",
|
||||
"tagline": "AI-powered research"
|
||||
},
|
||||
"nav": {
|
||||
"questions": "Questions",
|
||||
"collections": "Collections",
|
||||
"research": "Research",
|
||||
"settings": "Settings"
|
||||
},
|
||||
"question": {
|
||||
"create": "Create question",
|
||||
"edit": "Edit question",
|
||||
"delete": "Delete question",
|
||||
"title": "Question",
|
||||
"description": "Description",
|
||||
"status": "Status",
|
||||
"priority": "Priority",
|
||||
"noQuestions": "No questions yet",
|
||||
"addFirst": "Ask your first question"
|
||||
},
|
||||
"status": {
|
||||
"open": "Open",
|
||||
"researching": "Researching",
|
||||
"answered": "Answered",
|
||||
"archived": "Archived"
|
||||
},
|
||||
"priority": {
|
||||
"low": "Low",
|
||||
"medium": "Medium",
|
||||
"high": "High"
|
||||
},
|
||||
"collection": {
|
||||
"create": "Create collection",
|
||||
"edit": "Edit collection",
|
||||
"delete": "Delete collection",
|
||||
"name": "Name",
|
||||
"color": "Color",
|
||||
"noCollections": "No collections"
|
||||
},
|
||||
"research": {
|
||||
"start": "Start research",
|
||||
"inProgress": "Research in progress...",
|
||||
"depth": "Research depth",
|
||||
"quick": "Quick",
|
||||
"standard": "Standard",
|
||||
"deep": "Deep",
|
||||
"sources": "Sources",
|
||||
"summary": "Summary",
|
||||
"keyPoints": "Key points",
|
||||
"followUp": "Follow-up questions"
|
||||
},
|
||||
"answer": {
|
||||
"create": "Create answer",
|
||||
"edit": "Edit answer",
|
||||
"accept": "Accept answer",
|
||||
"rate": "Rate",
|
||||
"noAnswer": "No answer yet"
|
||||
},
|
||||
"source": {
|
||||
"view": "View source",
|
||||
"extract": "Extract content",
|
||||
"noSources": "No sources found"
|
||||
},
|
||||
"auth": {
|
||||
"login": "Login",
|
||||
"logout": "Logout",
|
||||
"register": "Register"
|
||||
},
|
||||
"common": {
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"delete": "Delete",
|
||||
"edit": "Edit",
|
||||
"add": "Add",
|
||||
"close": "Close",
|
||||
"search": "Search",
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
"loading": "Loading..."
|
||||
},
|
||||
"errors": {
|
||||
"loadQuestions": "Failed to load questions",
|
||||
"researchFailed": "Research failed",
|
||||
"saveFailed": "Failed to save",
|
||||
"loadSources": "Failed to load sources"
|
||||
},
|
||||
"success": {
|
||||
"questionCreated": "Question created",
|
||||
"questionDeleted": "Question deleted",
|
||||
"researchStarted": "Research started",
|
||||
"answerAccepted": "Answer accepted"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
<script lang="ts">
|
||||
import '../app.css';
|
||||
import '$lib/i18n';
|
||||
import { onMount } from 'svelte';
|
||||
import { isLoading as i18nLoading } from 'svelte-i18n';
|
||||
import { theme } from '$lib/stores/theme';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { apiClient } from '$lib/api/client';
|
||||
|
|
@ -9,6 +11,7 @@
|
|||
let { children } = $props();
|
||||
|
||||
let loading = $state(true);
|
||||
let appReady = $derived(!loading && !$i18nLoading);
|
||||
|
||||
onMount(async () => {
|
||||
theme.initialize();
|
||||
|
|
@ -24,7 +27,7 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
{#if loading}
|
||||
{#if !appReady}
|
||||
<AppLoadingSkeleton />
|
||||
{:else}
|
||||
<div class="min-h-screen bg-background text-foreground">
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@
|
|||
"@manacore/shared-theme": "workspace:*",
|
||||
"@manacore/shared-utils": "workspace:*",
|
||||
"idb": "^8.0.0",
|
||||
"svelte-i18n": "^4.0.1",
|
||||
"uuid": "^11.0.0"
|
||||
},
|
||||
"type": "module"
|
||||
|
|
|
|||
49
apps/skilltree/apps/web/src/lib/i18n/index.ts
Normal file
49
apps/skilltree/apps/web/src/lib/i18n/index.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
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 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'));
|
||||
|
||||
// Get initial locale from browser or localStorage
|
||||
function getInitialLocale(): SupportedLocale {
|
||||
if (browser) {
|
||||
// Check localStorage first
|
||||
const stored = localStorage.getItem('skilltree_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('skilltree_locale', newLocale);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for locale to be loaded (useful for SSR)
|
||||
export { waitLocale };
|
||||
83
apps/skilltree/apps/web/src/lib/i18n/locales/de.json
Normal file
83
apps/skilltree/apps/web/src/lib/i18n/locales/de.json
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
{
|
||||
"app": {
|
||||
"name": "SkillTree",
|
||||
"loading": "Laden...",
|
||||
"tagline": "Level Up Your Life"
|
||||
},
|
||||
"nav": {
|
||||
"skills": "Skills",
|
||||
"activities": "Aktivitäten",
|
||||
"stats": "Statistiken",
|
||||
"settings": "Einstellungen"
|
||||
},
|
||||
"skill": {
|
||||
"create": "Skill erstellen",
|
||||
"edit": "Skill bearbeiten",
|
||||
"delete": "Skill löschen",
|
||||
"name": "Name",
|
||||
"description": "Beschreibung",
|
||||
"branch": "Kategorie",
|
||||
"level": "Level",
|
||||
"xp": "XP",
|
||||
"totalXp": "Gesamt-XP",
|
||||
"noSkills": "Noch keine Skills",
|
||||
"addFirst": "Füge deinen ersten Skill hinzu"
|
||||
},
|
||||
"branch": {
|
||||
"intellect": "Intellekt",
|
||||
"body": "Körper",
|
||||
"creativity": "Kreativität",
|
||||
"social": "Soziales",
|
||||
"practical": "Praktisches",
|
||||
"mindset": "Mindset"
|
||||
},
|
||||
"level": {
|
||||
"unknown": "Unbekannt",
|
||||
"beginner": "Anfänger",
|
||||
"intermediate": "Fortgeschritten",
|
||||
"competent": "Kompetent",
|
||||
"expert": "Experte",
|
||||
"master": "Meister"
|
||||
},
|
||||
"activity": {
|
||||
"log": "Aktivität loggen",
|
||||
"recent": "Letzte Aktivitäten",
|
||||
"xpEarned": "+{xp} XP",
|
||||
"noActivities": "Noch keine Aktivitäten"
|
||||
},
|
||||
"stats": {
|
||||
"totalXp": "Gesamt-XP",
|
||||
"totalSkills": "Skills",
|
||||
"highestLevel": "Höchstes Level",
|
||||
"streak": "Streak"
|
||||
},
|
||||
"auth": {
|
||||
"login": "Anmelden",
|
||||
"logout": "Abmelden",
|
||||
"register": "Registrieren"
|
||||
},
|
||||
"common": {
|
||||
"save": "Speichern",
|
||||
"cancel": "Abbrechen",
|
||||
"delete": "Löschen",
|
||||
"edit": "Bearbeiten",
|
||||
"add": "Hinzufügen",
|
||||
"close": "Schließen",
|
||||
"search": "Suchen",
|
||||
"error": "Fehler",
|
||||
"success": "Erfolgreich",
|
||||
"loading": "Laden..."
|
||||
},
|
||||
"errors": {
|
||||
"loadSkills": "Skills konnten nicht geladen werden",
|
||||
"createSkill": "Skill konnte nicht erstellt werden",
|
||||
"updateSkill": "Skill konnte nicht aktualisiert werden",
|
||||
"deleteSkill": "Skill konnte nicht gelöscht werden"
|
||||
},
|
||||
"success": {
|
||||
"skillCreated": "Skill erstellt",
|
||||
"skillUpdated": "Skill aktualisiert",
|
||||
"skillDeleted": "Skill gelöscht",
|
||||
"xpAdded": "XP hinzugefügt"
|
||||
}
|
||||
}
|
||||
83
apps/skilltree/apps/web/src/lib/i18n/locales/en.json
Normal file
83
apps/skilltree/apps/web/src/lib/i18n/locales/en.json
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
{
|
||||
"app": {
|
||||
"name": "SkillTree",
|
||||
"loading": "Loading...",
|
||||
"tagline": "Level Up Your Life"
|
||||
},
|
||||
"nav": {
|
||||
"skills": "Skills",
|
||||
"activities": "Activities",
|
||||
"stats": "Statistics",
|
||||
"settings": "Settings"
|
||||
},
|
||||
"skill": {
|
||||
"create": "Create skill",
|
||||
"edit": "Edit skill",
|
||||
"delete": "Delete skill",
|
||||
"name": "Name",
|
||||
"description": "Description",
|
||||
"branch": "Category",
|
||||
"level": "Level",
|
||||
"xp": "XP",
|
||||
"totalXp": "Total XP",
|
||||
"noSkills": "No skills yet",
|
||||
"addFirst": "Add your first skill"
|
||||
},
|
||||
"branch": {
|
||||
"intellect": "Intellect",
|
||||
"body": "Body",
|
||||
"creativity": "Creativity",
|
||||
"social": "Social",
|
||||
"practical": "Practical",
|
||||
"mindset": "Mindset"
|
||||
},
|
||||
"level": {
|
||||
"unknown": "Unknown",
|
||||
"beginner": "Beginner",
|
||||
"intermediate": "Intermediate",
|
||||
"competent": "Competent",
|
||||
"expert": "Expert",
|
||||
"master": "Master"
|
||||
},
|
||||
"activity": {
|
||||
"log": "Log activity",
|
||||
"recent": "Recent activities",
|
||||
"xpEarned": "+{xp} XP",
|
||||
"noActivities": "No activities yet"
|
||||
},
|
||||
"stats": {
|
||||
"totalXp": "Total XP",
|
||||
"totalSkills": "Skills",
|
||||
"highestLevel": "Highest Level",
|
||||
"streak": "Streak"
|
||||
},
|
||||
"auth": {
|
||||
"login": "Login",
|
||||
"logout": "Logout",
|
||||
"register": "Register"
|
||||
},
|
||||
"common": {
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"delete": "Delete",
|
||||
"edit": "Edit",
|
||||
"add": "Add",
|
||||
"close": "Close",
|
||||
"search": "Search",
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
"loading": "Loading..."
|
||||
},
|
||||
"errors": {
|
||||
"loadSkills": "Failed to load skills",
|
||||
"createSkill": "Failed to create skill",
|
||||
"updateSkill": "Failed to update skill",
|
||||
"deleteSkill": "Failed to delete skill"
|
||||
},
|
||||
"success": {
|
||||
"skillCreated": "Skill created",
|
||||
"skillUpdated": "Skill updated",
|
||||
"skillDeleted": "Skill deleted",
|
||||
"xpAdded": "XP added"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,15 @@
|
|||
<script lang="ts">
|
||||
import '../app.css';
|
||||
import '$lib/i18n';
|
||||
import { onMount } from 'svelte';
|
||||
import { isLoading as i18nLoading, _ as t } from 'svelte-i18n';
|
||||
import { skillStore } from '$lib/stores/skills.svelte';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
let loading = $state(true);
|
||||
let appReady = $derived(!loading && !$i18nLoading);
|
||||
|
||||
onMount(async () => {
|
||||
await Promise.all([authStore.initialize(), skillStore.initialize()]);
|
||||
|
|
@ -15,15 +18,15 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>SkillTree - Level Up Your Life</title>
|
||||
<title>{$t('app.name')} - {$t('app.tagline')}</title>
|
||||
<meta name="description" content="Track your skills like a game. Level up in real life." />
|
||||
</svelte:head>
|
||||
|
||||
{#if loading}
|
||||
{#if !appReady}
|
||||
<div class="flex min-h-screen items-center justify-center bg-gray-900">
|
||||
<div class="text-center">
|
||||
<div class="mb-4 text-6xl">🌳</div>
|
||||
<div class="text-xl text-gray-300">Loading SkillTree...</div>
|
||||
<div class="text-xl text-gray-300">{$t('app.loading')}</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
|
|
|
|||
49
apps/todo/apps/web/src/lib/i18n/index.ts
Normal file
49
apps/todo/apps/web/src/lib/i18n/index.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
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 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'));
|
||||
|
||||
// Get initial locale from browser or localStorage
|
||||
function getInitialLocale(): SupportedLocale {
|
||||
if (browser) {
|
||||
// Check localStorage first
|
||||
const stored = localStorage.getItem('todo_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('todo_locale', newLocale);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for locale to be loaded (useful for SSR)
|
||||
export { waitLocale };
|
||||
104
apps/todo/apps/web/src/lib/i18n/locales/de.json
Normal file
104
apps/todo/apps/web/src/lib/i18n/locales/de.json
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
{
|
||||
"app": {
|
||||
"name": "Todo",
|
||||
"loading": "Laden..."
|
||||
},
|
||||
"nav": {
|
||||
"inbox": "Eingang",
|
||||
"today": "Heute",
|
||||
"upcoming": "Anstehend",
|
||||
"projects": "Projekte",
|
||||
"labels": "Labels",
|
||||
"completed": "Erledigt",
|
||||
"settings": "Einstellungen",
|
||||
"feedback": "Feedback"
|
||||
},
|
||||
"task": {
|
||||
"title": "Titel",
|
||||
"description": "Beschreibung",
|
||||
"dueDate": "Fällig am",
|
||||
"dueTime": "Uhrzeit",
|
||||
"priority": "Priorität",
|
||||
"project": "Projekt",
|
||||
"labels": "Labels",
|
||||
"subtasks": "Teilaufgaben",
|
||||
"reminder": "Erinnerung",
|
||||
"repeat": "Wiederholen",
|
||||
"addTask": "Aufgabe hinzufügen",
|
||||
"editTask": "Aufgabe bearbeiten",
|
||||
"deleteTask": "Aufgabe löschen",
|
||||
"completeTask": "Aufgabe erledigen",
|
||||
"uncompleteTask": "Als unerledigt markieren",
|
||||
"noTasks": "Keine Aufgaben",
|
||||
"noTasksToday": "Keine Aufgaben für heute",
|
||||
"noTasksUpcoming": "Keine anstehenden Aufgaben"
|
||||
},
|
||||
"project": {
|
||||
"create": "Projekt erstellen",
|
||||
"edit": "Projekt bearbeiten",
|
||||
"delete": "Projekt löschen",
|
||||
"name": "Name",
|
||||
"color": "Farbe",
|
||||
"icon": "Symbol",
|
||||
"archive": "Archivieren",
|
||||
"noProjects": "Keine Projekte"
|
||||
},
|
||||
"label": {
|
||||
"create": "Label erstellen",
|
||||
"edit": "Label bearbeiten",
|
||||
"delete": "Label löschen",
|
||||
"name": "Name",
|
||||
"color": "Farbe",
|
||||
"noLabels": "Keine Labels"
|
||||
},
|
||||
"priority": {
|
||||
"urgent": "Dringend",
|
||||
"high": "Hoch",
|
||||
"medium": "Normal",
|
||||
"low": "Niedrig"
|
||||
},
|
||||
"repeat": {
|
||||
"none": "Nicht wiederholen",
|
||||
"daily": "Täglich",
|
||||
"weekly": "Wöchentlich",
|
||||
"monthly": "Monatlich",
|
||||
"yearly": "Jährlich"
|
||||
},
|
||||
"auth": {
|
||||
"login": "Anmelden",
|
||||
"logout": "Abmelden",
|
||||
"register": "Registrieren",
|
||||
"email": "E-Mail",
|
||||
"password": "Passwort",
|
||||
"forgotPassword": "Passwort vergessen?"
|
||||
},
|
||||
"common": {
|
||||
"save": "Speichern",
|
||||
"cancel": "Abbrechen",
|
||||
"delete": "Löschen",
|
||||
"edit": "Bearbeiten",
|
||||
"add": "Hinzufügen",
|
||||
"close": "Schließen",
|
||||
"search": "Suchen",
|
||||
"error": "Fehler",
|
||||
"success": "Erfolgreich",
|
||||
"loading": "Laden...",
|
||||
"noResults": "Keine Ergebnisse"
|
||||
},
|
||||
"errors": {
|
||||
"loadTasks": "Aufgaben konnten nicht geladen werden",
|
||||
"createTask": "Aufgabe konnte nicht erstellt werden",
|
||||
"updateTask": "Aufgabe konnte nicht aktualisiert werden",
|
||||
"deleteTask": "Aufgabe konnte nicht gelöscht werden",
|
||||
"loadProjects": "Projekte konnten nicht geladen werden",
|
||||
"loadLabels": "Labels konnten nicht geladen werden"
|
||||
},
|
||||
"success": {
|
||||
"taskCreated": "Aufgabe erstellt",
|
||||
"taskUpdated": "Aufgabe aktualisiert",
|
||||
"taskDeleted": "Aufgabe gelöscht",
|
||||
"taskCompleted": "Aufgabe erledigt",
|
||||
"projectCreated": "Projekt erstellt",
|
||||
"labelCreated": "Label erstellt"
|
||||
}
|
||||
}
|
||||
104
apps/todo/apps/web/src/lib/i18n/locales/en.json
Normal file
104
apps/todo/apps/web/src/lib/i18n/locales/en.json
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
{
|
||||
"app": {
|
||||
"name": "Todo",
|
||||
"loading": "Loading..."
|
||||
},
|
||||
"nav": {
|
||||
"inbox": "Inbox",
|
||||
"today": "Today",
|
||||
"upcoming": "Upcoming",
|
||||
"projects": "Projects",
|
||||
"labels": "Labels",
|
||||
"completed": "Completed",
|
||||
"settings": "Settings",
|
||||
"feedback": "Feedback"
|
||||
},
|
||||
"task": {
|
||||
"title": "Title",
|
||||
"description": "Description",
|
||||
"dueDate": "Due date",
|
||||
"dueTime": "Time",
|
||||
"priority": "Priority",
|
||||
"project": "Project",
|
||||
"labels": "Labels",
|
||||
"subtasks": "Subtasks",
|
||||
"reminder": "Reminder",
|
||||
"repeat": "Repeat",
|
||||
"addTask": "Add task",
|
||||
"editTask": "Edit task",
|
||||
"deleteTask": "Delete task",
|
||||
"completeTask": "Complete task",
|
||||
"uncompleteTask": "Mark as incomplete",
|
||||
"noTasks": "No tasks",
|
||||
"noTasksToday": "No tasks for today",
|
||||
"noTasksUpcoming": "No upcoming tasks"
|
||||
},
|
||||
"project": {
|
||||
"create": "Create project",
|
||||
"edit": "Edit project",
|
||||
"delete": "Delete project",
|
||||
"name": "Name",
|
||||
"color": "Color",
|
||||
"icon": "Icon",
|
||||
"archive": "Archive",
|
||||
"noProjects": "No projects"
|
||||
},
|
||||
"label": {
|
||||
"create": "Create label",
|
||||
"edit": "Edit label",
|
||||
"delete": "Delete label",
|
||||
"name": "Name",
|
||||
"color": "Color",
|
||||
"noLabels": "No labels"
|
||||
},
|
||||
"priority": {
|
||||
"urgent": "Urgent",
|
||||
"high": "High",
|
||||
"medium": "Normal",
|
||||
"low": "Low"
|
||||
},
|
||||
"repeat": {
|
||||
"none": "Don't repeat",
|
||||
"daily": "Daily",
|
||||
"weekly": "Weekly",
|
||||
"monthly": "Monthly",
|
||||
"yearly": "Yearly"
|
||||
},
|
||||
"auth": {
|
||||
"login": "Login",
|
||||
"logout": "Logout",
|
||||
"register": "Register",
|
||||
"email": "Email",
|
||||
"password": "Password",
|
||||
"forgotPassword": "Forgot password?"
|
||||
},
|
||||
"common": {
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"delete": "Delete",
|
||||
"edit": "Edit",
|
||||
"add": "Add",
|
||||
"close": "Close",
|
||||
"search": "Search",
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
"loading": "Loading...",
|
||||
"noResults": "No results"
|
||||
},
|
||||
"errors": {
|
||||
"loadTasks": "Failed to load tasks",
|
||||
"createTask": "Failed to create task",
|
||||
"updateTask": "Failed to update task",
|
||||
"deleteTask": "Failed to delete task",
|
||||
"loadProjects": "Failed to load projects",
|
||||
"loadLabels": "Failed to load labels"
|
||||
},
|
||||
"success": {
|
||||
"taskCreated": "Task created",
|
||||
"taskUpdated": "Task updated",
|
||||
"taskDeleted": "Task deleted",
|
||||
"taskCompleted": "Task completed",
|
||||
"projectCreated": "Project created",
|
||||
"labelCreated": "Label created"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
<script lang="ts">
|
||||
import '../app.css';
|
||||
import '$lib/i18n';
|
||||
import { onMount } from 'svelte';
|
||||
import { isLoading as i18nLoading } from 'svelte-i18n';
|
||||
import { theme } from '$lib/stores/theme';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { AppLoadingSkeleton } from '$lib/components/skeletons';
|
||||
|
|
@ -8,6 +10,7 @@
|
|||
let { children } = $props();
|
||||
|
||||
let loading = $state(true);
|
||||
let appReady = $derived(!loading && !$i18nLoading);
|
||||
|
||||
onMount(async () => {
|
||||
// Initialize theme
|
||||
|
|
@ -20,7 +23,7 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
{#if loading}
|
||||
{#if !appReady}
|
||||
<AppLoadingSkeleton />
|
||||
{:else}
|
||||
<div class="min-h-screen bg-background text-foreground">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue