🌐 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:
Till-JS 2026-01-29 14:47:58 +01:00
parent a938ed86d4
commit 5a0815708c
35 changed files with 3440 additions and 56 deletions

View file

@ -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"

View 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 };

View 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"
}
}

View 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"
}
}

View file

@ -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}