diff --git a/apps/calendar/apps/web/src/lib/content/help/index.test.ts b/apps/calendar/apps/web/src/lib/content/help/index.test.ts
new file mode 100644
index 000000000..cfb673b12
--- /dev/null
+++ b/apps/calendar/apps/web/src/lib/content/help/index.test.ts
@@ -0,0 +1,47 @@
+import { describe, it, expect } from 'vitest';
+import { getCalendarHelpContent } from './index';
+
+describe('Calendar Help Content', () => {
+ it('returns valid German content', () => {
+ const content = getCalendarHelpContent('de');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ expect(content.contact.supportEmail).toBe('support@mana.how');
+ });
+
+ it('returns valid English content', () => {
+ const content = getCalendarHelpContent('en');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ });
+
+ it('returns same number of FAQ items for both languages', () => {
+ const de = getCalendarHelpContent('de');
+ const en = getCalendarHelpContent('en');
+
+ expect(de.faq.length).toBe(en.faq.length);
+ expect(de.features.length).toBe(en.features.length);
+ });
+
+ it('has unique FAQ IDs', () => {
+ const content = getCalendarHelpContent('de');
+ const ids = content.faq.map((f) => f.id);
+ expect(new Set(ids).size).toBe(ids.length);
+ });
+});
diff --git a/apps/calendar/apps/web/src/lib/content/help/index.ts b/apps/calendar/apps/web/src/lib/content/help/index.ts
index 81b7fd969..906656f8f 100644
--- a/apps/calendar/apps/web/src/lib/content/help/index.ts
+++ b/apps/calendar/apps/web/src/lib/content/help/index.ts
@@ -3,6 +3,7 @@
*/
import type { HelpContent } from '@manacore/shared-help-types';
+import { getPrivacyFAQs } from '@manacore/shared-help-types';
export function getCalendarHelpContent(locale: string): HelpContent {
const isDE = locale === 'de';
@@ -82,19 +83,7 @@ export function getCalendarHelpContent(locale: string): HelpContent {
? ['sync', 'caldav', 'google', 'apple', 'extern']
: ['sync', 'caldav', 'google', 'apple', 'external'],
},
- {
- id: 'faq-privacy',
- question: t('Wie werden meine Daten geschützt?', 'How is my data protected?'),
- answer: t(
- '
Deine Daten sind sicher:
- Verschlüsselung: Alle Daten werden bei der Übertragung (TLS) verschlüsselt
- DSGVO-konform: Wir halten uns an die EU-Datenschutzverordnung
- Kein Datenverkauf: Deine Kalender-Daten werden nie an Dritte verkauft
- Export: Du kannst jederzeit alle Kalender als iCal exportieren
',
- 'Your data is secure:
- Encryption: All data is encrypted in transit (TLS)
- GDPR Compliant: We follow EU data protection regulations
- No Data Selling: Your calendar data is never sold to third parties
- Export: You can export all calendars as iCal anytime
'
- ),
- category: 'privacy',
- order: 5,
- language: isDE ? 'de' : 'en',
- featured: true,
- tags: isDE ? ['datenschutz', 'dsgvo', 'sicherheit'] : ['privacy', 'gdpr', 'security'],
- },
+ ...getPrivacyFAQs(locale, { dataTypeDE: 'Kalenderdaten', dataTypeEN: 'calendar data' }),
],
features: [
{
diff --git a/apps/chat/apps/web/src/lib/content/help/index.test.ts b/apps/chat/apps/web/src/lib/content/help/index.test.ts
new file mode 100644
index 000000000..23b833183
--- /dev/null
+++ b/apps/chat/apps/web/src/lib/content/help/index.test.ts
@@ -0,0 +1,47 @@
+import { describe, it, expect } from 'vitest';
+import { getChatHelpContent } from './index';
+
+describe('Chat Help Content', () => {
+ it('returns valid German content', () => {
+ const content = getChatHelpContent('de');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ expect(content.contact.supportEmail).toBe('support@mana.how');
+ });
+
+ it('returns valid English content', () => {
+ const content = getChatHelpContent('en');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ });
+
+ it('returns same number of FAQ items for both languages', () => {
+ const de = getChatHelpContent('de');
+ const en = getChatHelpContent('en');
+
+ expect(de.faq.length).toBe(en.faq.length);
+ expect(de.features.length).toBe(en.features.length);
+ });
+
+ it('has unique FAQ IDs', () => {
+ const content = getChatHelpContent('de');
+ const ids = content.faq.map((f) => f.id);
+ expect(new Set(ids).size).toBe(ids.length);
+ });
+});
diff --git a/apps/chat/apps/web/src/lib/content/help/index.ts b/apps/chat/apps/web/src/lib/content/help/index.ts
index 3e48e2952..4a5643bb4 100644
--- a/apps/chat/apps/web/src/lib/content/help/index.ts
+++ b/apps/chat/apps/web/src/lib/content/help/index.ts
@@ -3,6 +3,7 @@
*/
import type { HelpContent } from '@manacore/shared-help-types';
+import { getPrivacyFAQs } from '@manacore/shared-help-types';
export function getChatHelpContent(locale: string): HelpContent {
const isDE = locale === 'de';
@@ -56,18 +57,16 @@ export function getChatHelpContent(locale: string): HelpContent {
language: isDE ? 'de' : 'en',
tags: isDE ? ['vergleich', 'modelle', 'test'] : ['compare', 'models', 'test'],
},
- {
- id: 'faq-privacy',
- question: isDE ? 'Wie werden meine Chats geschützt?' : 'How are my chats protected?',
- answer: isDE
- ? 'Deine Unterhaltungen sind sicher:
- Verschlüsselung: Alle Daten werden bei der Übertragung (TLS) verschlüsselt
- Privat: Nur du hast Zugriff auf deine Chats
- Lokale Modelle: Bei lokalen Modellen verlassen deine Daten nie unseren Server
- DSGVO-konform: Wir halten uns an die EU-Datenschutzverordnung
'
- : 'Your conversations are secure:
- Encryption: All data is encrypted in transit (TLS)
- Private: Only you have access to your chats
- Local models: With local models, your data never leaves our server
- GDPR Compliant: We follow EU data protection regulations
',
- category: 'privacy',
- order: 5,
- language: isDE ? 'de' : 'en',
- featured: true,
- tags: isDE ? ['datenschutz', 'sicherheit', 'dsgvo'] : ['privacy', 'security', 'gdpr'],
- },
+ ...getPrivacyFAQs(locale, {
+ dataTypeDE: 'Chats',
+ dataTypeEN: 'chats',
+ extraBulletsDE: [
+ 'Lokale Modelle: Bei lokalen Modellen verlassen deine Daten nie unseren Server',
+ ],
+ extraBulletsEN: [
+ 'Local models: With local models, your data never leaves our server',
+ ],
+ }),
],
features: [
{
diff --git a/apps/citycorners/apps/web/src/lib/content/help/index.test.ts b/apps/citycorners/apps/web/src/lib/content/help/index.test.ts
new file mode 100644
index 000000000..83c1166da
--- /dev/null
+++ b/apps/citycorners/apps/web/src/lib/content/help/index.test.ts
@@ -0,0 +1,47 @@
+import { describe, it, expect } from 'vitest';
+import { getCityCornersHelpContent } from './index';
+
+describe('CityCorners Help Content', () => {
+ it('returns valid German content', () => {
+ const content = getCityCornersHelpContent('de');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ expect(content.contact.supportEmail).toBe('support@mana.how');
+ });
+
+ it('returns valid English content', () => {
+ const content = getCityCornersHelpContent('en');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ });
+
+ it('returns same number of FAQ items for both languages', () => {
+ const de = getCityCornersHelpContent('de');
+ const en = getCityCornersHelpContent('en');
+
+ expect(de.faq.length).toBe(en.faq.length);
+ expect(de.features.length).toBe(en.features.length);
+ });
+
+ it('has unique FAQ IDs', () => {
+ const content = getCityCornersHelpContent('de');
+ const ids = content.faq.map((f) => f.id);
+ expect(new Set(ids).size).toBe(ids.length);
+ });
+});
diff --git a/apps/citycorners/apps/web/src/lib/content/help/index.ts b/apps/citycorners/apps/web/src/lib/content/help/index.ts
new file mode 100644
index 000000000..1a2ca0064
--- /dev/null
+++ b/apps/citycorners/apps/web/src/lib/content/help/index.ts
@@ -0,0 +1,258 @@
+/**
+ * Help content for CityCorners app
+ */
+
+import type { HelpContent } from '@manacore/shared-help-types';
+import { getPrivacyFAQs } from '@manacore/shared-help-types';
+
+export function getCityCornersHelpContent(locale: string): HelpContent {
+ const isDE = locale === 'de';
+
+ return {
+ faq: [
+ {
+ id: 'faq-locations',
+ question: isDE ? 'Wie finde ich Orte in Konstanz?' : 'How do I find places in Konstanz?',
+ answer: isDE
+ ? 'CityCorners zeigt dir die besten Orte in Konstanz:
- Nutze die Kategoriefilter (Sehenswürdigkeiten, Restaurants, Cafés, Bars, Parks, Museen, etc.)
- Durchsuche die Karte mit farbcodierten Markern
- Nutze die Suche in der QuickInputBar für gezielte Ergebnisse
- 11 verschiedene Kategorien stehen zur Verfügung
'
+ : 'CityCorners shows you the best places in Konstanz:
- Use category filters (sights, restaurants, cafés, bars, parks, museums, etc.)
- Browse the map with color-coded markers
- Use search in the QuickInputBar for targeted results
- 11 different categories are available
',
+ category: 'features',
+ order: 1,
+ language: isDE ? 'de' : 'en',
+ tags: isDE ? ['orte', 'suche', 'kategorien'] : ['locations', 'search', 'categories'],
+ },
+ {
+ id: 'faq-map',
+ question: isDE ? 'Wie funktioniert die Kartenansicht?' : 'How does the map view work?',
+ answer: isDE
+ ? 'Die interaktive Leaflet-Karte zeigt alle Orte mit farbcodierten Markern nach Kategorie:
- Blau — Sehenswürdigkeiten
- Rot — Restaurants
- Gelb — Cafés
- Orange — Bars
- Grün — Parks und Läden
- Klicke auf einen Marker für Details und Wegbeschreibung
'
+ : 'The interactive Leaflet map shows all locations with color-coded markers by category:
- Blue — Sights
- Red — Restaurants
- Yellow — Cafés
- Orange — Bars
- Green — Parks and shops
- Click a marker for details and directions
',
+ category: 'features',
+ order: 2,
+ language: isDE ? 'de' : 'en',
+ tags: isDE ? ['karte', 'marker', 'navigation'] : ['map', 'markers', 'navigation'],
+ },
+ {
+ id: 'faq-favorites',
+ question: isDE ? 'Wie speichere ich Favoriten?' : 'How do I save favorites?',
+ answer: isDE
+ ? 'Melde dich an und klicke auf das Herz-Symbol bei einem Ort, um ihn als Favorit zu speichern. Alle Favoriten findest du im Bereich Favoriten. Favoriten werden optimistisch aktualisiert — du siehst die Änderung sofort.
'
+ : 'Sign in and click the heart icon on a location to save it as a favorite. Find all favorites in the Favorites section. Favorites are optimistically updated — you see the change immediately.
',
+ category: 'features',
+ order: 3,
+ language: isDE ? 'de' : 'en',
+ tags: isDE ? ['favoriten', 'speichern', 'merken'] : ['favorites', 'save', 'bookmarks'],
+ },
+ {
+ id: 'faq-add-location',
+ question: isDE ? 'Kann ich eigene Orte hinzufügen?' : 'Can I add my own locations?',
+ answer: isDE
+ ? 'Ja! Melde dich an und nutze die Funktion Ort hinzufügen:
- Suche den Ort per Web-Lookup — Informationen werden automatisch aus dem Web extrahiert
- Ergänze oder bearbeite die Informationen (Name, Beschreibung, Adresse)
- Wähle eine der 11 Kategorien
- Sende den Ort ab
Der Web-Lookup nutzt unseren eigenen Suchdienst (mana-search).
'
+ : 'Yes! Sign in and use the Add Location feature:
- Search for the place via web lookup — information is automatically extracted from the web
- Complete or edit the information (name, description, address)
- Choose one of the 11 categories
- Submit the location
The web lookup uses our own search service (mana-search).
',
+ category: 'features',
+ order: 4,
+ language: isDE ? 'de' : 'en',
+ tags: isDE ? ['hinzufügen', 'beitragen', 'orte'] : ['add', 'contribute', 'locations'],
+ },
+ {
+ id: 'faq-detail-timeline',
+ question: isDE ? 'Was zeigen die Detailseiten?' : 'What do detail pages show?',
+ answer: isDE
+ ? 'Jeder Ort hat eine ausführliche Detailseite:
- Beschreibung und Adresse
- Mini-Karte mit dem Standort
- Timeline — Historische Ereignisse des Ortes (z.B. Gründungsjahr, wichtige Ereignisse)
- Favoriten-Button zum Speichern
- Möglichkeit, den Ort zu bearbeiten
'
+ : 'Each location has a detailed page:
- Description and address
- Mini-map with the location
- Timeline — Historical events of the place (e.g., founding year, important events)
- Favorite button to save
- Option to edit the location
',
+ category: 'features',
+ order: 5,
+ language: isDE ? 'de' : 'en',
+ tags: isDE ? ['details', 'timeline', 'historie'] : ['details', 'timeline', 'history'],
+ },
+ {
+ id: 'faq-categories',
+ question: isDE ? 'Welche Kategorien gibt es?' : 'What categories are available?',
+ answer: isDE
+ ? 'CityCorners bietet 11 Kategorien:
- Sehenswürdigkeit, Restaurant, Laden, Museum, Café
- Bar, Park, Strandbad, Hotel, Veranstaltungsort, Aussichtspunkt
Jede Kategorie hat eine eigene Farbe auf der Karte und in den Karten-Ansichten.
'
+ : 'CityCorners offers 11 categories:
- Sight, Restaurant, Shop, Museum, Café
- Bar, Park, Beach, Hotel, Event Venue, Viewpoint
Each category has its own color on the map and in card views.
',
+ category: 'features',
+ order: 6,
+ language: isDE ? 'de' : 'en',
+ tags: isDE ? ['kategorien', 'typen', 'filter'] : ['categories', 'types', 'filter'],
+ },
+ {
+ id: 'faq-i18n',
+ question: isDE ? 'Ist CityCorners mehrsprachig?' : 'Is CityCorners multilingual?',
+ answer: isDE
+ ? 'Ja! CityCorners ist in Deutsch und Englisch verfügbar. Du kannst die Sprache jederzeit über den Sprachumschalter in der Navigation ändern. Die Einstellung wird lokal gespeichert.
'
+ : 'Yes! CityCorners is available in German and English. You can change the language at any time via the language switcher in the navigation. The setting is saved locally.
',
+ category: 'general',
+ order: 7,
+ language: isDE ? 'de' : 'en',
+ tags: isDE ? ['sprache', 'deutsch', 'englisch'] : ['language', 'german', 'english'],
+ },
+ {
+ id: 'faq-feedback',
+ question: isDE ? 'Wie kann ich Feedback geben?' : 'How can I give feedback?',
+ answer: isDE
+ ? 'Dein Feedback hilft uns, CityCorners zu verbessern:
- Nutze die Feedback-Seite im Menü, um Verbesserungsvorschläge, Fehlermeldungen oder neue Ort-Vorschläge einzureichen
- Wir lesen jedes Feedback und arbeiten kontinuierlich an Verbesserungen
'
+ : 'Your feedback helps us improve CityCorners:
- Use the Feedback page in the menu to submit improvement suggestions, bug reports, or new location ideas
- We read every piece of feedback and continuously work on improvements
',
+ category: 'general',
+ order: 8,
+ language: isDE ? 'de' : 'en',
+ tags: isDE
+ ? ['feedback', 'verbesserung', 'kontakt']
+ : ['feedback', 'improvement', 'contact'],
+ },
+ ...getPrivacyFAQs(locale, { dataTypeDE: 'Daten', dataTypeEN: 'data' }),
+ ],
+ features: [
+ {
+ id: 'feature-explore',
+ title: isDE ? 'Orte entdecken' : 'Explore Places',
+ description: isDE
+ ? 'Entdecke Sehenswürdigkeiten, Restaurants, Cafés und mehr in Konstanz am Bodensee'
+ : 'Discover sights, restaurants, cafés, and more in Konstanz at Lake Constance',
+ icon: '🏛️',
+ category: 'core',
+ highlights: isDE
+ ? ['11 Kategorien', 'Detailseiten mit Timeline', 'Farbcodierte Karten', 'Suche']
+ : ['11 categories', 'Detail pages with timeline', 'Color-coded cards', 'Search'],
+ content: '',
+ order: 1,
+ language: isDE ? 'de' : 'en',
+ },
+ {
+ id: 'feature-map',
+ title: isDE ? 'Interaktive Karte' : 'Interactive Map',
+ description: isDE
+ ? 'Leaflet-basierte Karte mit farbcodierten Markern für alle Kategorien'
+ : 'Leaflet-based map with color-coded markers for all categories',
+ icon: '🗺️',
+ category: 'core',
+ highlights: isDE
+ ? ['Farbcodierte Marker', 'Kategorie-Filter', 'Detailansicht per Klick', 'Mini-Karte']
+ : ['Color-coded markers', 'Category filters', 'Detail view on click', 'Mini-map'],
+ content: '',
+ order: 2,
+ language: isDE ? 'de' : 'en',
+ },
+ {
+ id: 'feature-favorites',
+ title: isDE ? 'Favoriten' : 'Favorites',
+ description: isDE
+ ? 'Speichere deine Lieblingsorte und finde sie schnell wieder'
+ : 'Save your favorite places and find them quickly',
+ icon: '❤️',
+ category: 'core',
+ highlights: isDE
+ ? ['Herz-Button', 'Optimistische Updates', 'Dedizierte Ansicht']
+ : ['Heart button', 'Optimistic updates', 'Dedicated view'],
+ content: '',
+ order: 3,
+ language: isDE ? 'de' : 'en',
+ },
+ {
+ id: 'feature-add',
+ title: isDE ? 'Orte hinzufügen' : 'Add Locations',
+ description: isDE
+ ? 'Trage neue Orte bei — mit Web-Lookup für automatische Informationsextraktion'
+ : 'Contribute new locations — with web lookup for automatic information extraction',
+ icon: '➕',
+ category: 'core',
+ highlights: isDE
+ ? ['Web-Lookup', 'Auto-Extraktion', '11 Kategorien', 'Bearbeiten']
+ : ['Web lookup', 'Auto extraction', '11 categories', 'Editing'],
+ content: '',
+ order: 4,
+ language: isDE ? 'de' : 'en',
+ },
+ {
+ id: 'feature-timeline',
+ title: 'Timeline',
+ description: isDE
+ ? 'Historische Ereignisse und Meilensteine für jeden Ort'
+ : 'Historical events and milestones for every location',
+ icon: '📅',
+ category: 'advanced',
+ highlights: isDE
+ ? ['Historische Ereignisse', 'Gründungsjahre', 'Meilensteine']
+ : ['Historical events', 'Founding years', 'Milestones'],
+ content: '',
+ order: 5,
+ language: isDE ? 'de' : 'en',
+ },
+ {
+ id: 'feature-i18n',
+ title: isDE ? 'Mehrsprachig' : 'Multilingual',
+ description: isDE
+ ? 'Verfügbar in Deutsch und Englisch mit einfachem Sprachumschalter'
+ : 'Available in German and English with easy language switcher',
+ icon: '🌍',
+ category: 'core',
+ highlights: isDE
+ ? ['Deutsch & Englisch', 'Sofort umschaltbar', 'Lokal gespeichert']
+ : ['German & English', 'Instant switching', 'Locally saved'],
+ content: '',
+ order: 6,
+ language: isDE ? 'de' : 'en',
+ },
+ {
+ id: 'feature-pwa',
+ title: isDE ? 'Progressive Web App' : 'Progressive Web App',
+ description: isDE
+ ? 'Installierbar auf jedem Gerät — mit Offline-Fallback und Service Worker'
+ : 'Installable on any device — with offline fallback and service worker',
+ icon: '📱',
+ category: 'advanced',
+ highlights: isDE
+ ? ['Installierbar', 'Offline-Fallback', 'Service Worker Caching']
+ : ['Installable', 'Offline fallback', 'Service worker caching'],
+ content: '',
+ order: 7,
+ language: isDE ? 'de' : 'en',
+ },
+ {
+ id: 'feature-settings',
+ title: isDE ? 'Einstellungen' : 'Settings',
+ description: isDE
+ ? 'Passe CityCorners an — Theme-Modus, Theme-Variante und Kontoeinstellungen'
+ : 'Customize CityCorners — theme mode, theme variant, and account settings',
+ icon: '⚙️',
+ category: 'core',
+ highlights: isDE
+ ? ['Hell/Dunkel-Modus', 'Theme-Varianten', 'Kontoeinstellungen']
+ : ['Light/Dark mode', 'Theme variants', 'Account settings'],
+ content: '',
+ order: 8,
+ language: isDE ? 'de' : 'en',
+ },
+ {
+ id: 'feature-reviews',
+ title: isDE ? 'Bewertungen' : 'Reviews',
+ description: isDE
+ ? 'Bewerte Orte und lies Bewertungen anderer Nutzer'
+ : 'Rate locations and read reviews from other users',
+ icon: '⭐',
+ category: 'core',
+ highlights: isDE
+ ? ['Sternebewertung', 'Textbewertungen', 'Community-Feedback']
+ : ['Star ratings', 'Text reviews', 'Community feedback'],
+ content: '',
+ order: 9,
+ language: isDE ? 'de' : 'en',
+ },
+ ],
+ shortcuts: [],
+ gettingStarted: [],
+ changelog: [],
+ contact: {
+ id: 'contact-support',
+ title: isDE ? 'Support kontaktieren' : 'Contact Support',
+ content: isDE
+ ? 'Unser Support-Team hilft dir bei allen Fragen rund um CityCorners. Nutze auch die Feedback-Funktion im Menü, um uns direkt Verbesserungsvorschläge oder neue Ort-Vorschläge zu schicken.
'
+ : 'Our support team is here to help you with any questions about CityCorners. You can also use the feedback feature in the menu to send us improvement suggestions or new location ideas directly.
',
+ language: isDE ? 'de' : 'en',
+ order: 1,
+ supportEmail: 'support@mana.how',
+ documentationUrl: 'https://mana.how/docs',
+ responseTime: isDE ? 'Normalerweise innerhalb von 24 Stunden' : 'Usually within 24 hours',
+ },
+ };
+}
diff --git a/apps/clock/apps/web/src/lib/content/help/index.test.ts b/apps/clock/apps/web/src/lib/content/help/index.test.ts
new file mode 100644
index 000000000..a1e31e35e
--- /dev/null
+++ b/apps/clock/apps/web/src/lib/content/help/index.test.ts
@@ -0,0 +1,47 @@
+import { describe, it, expect } from 'vitest';
+import { getClockHelpContent } from './index';
+
+describe('Clock Help Content', () => {
+ it('returns valid German content', () => {
+ const content = getClockHelpContent('de');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ expect(content.contact.supportEmail).toBe('support@mana.how');
+ });
+
+ it('returns valid English content', () => {
+ const content = getClockHelpContent('en');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ });
+
+ it('returns same number of FAQ items for both languages', () => {
+ const de = getClockHelpContent('de');
+ const en = getClockHelpContent('en');
+
+ expect(de.faq.length).toBe(en.faq.length);
+ expect(de.features.length).toBe(en.features.length);
+ });
+
+ it('has unique FAQ IDs', () => {
+ const content = getClockHelpContent('de');
+ const ids = content.faq.map((f) => f.id);
+ expect(new Set(ids).size).toBe(ids.length);
+ });
+});
diff --git a/apps/clock/apps/web/src/lib/content/help/index.ts b/apps/clock/apps/web/src/lib/content/help/index.ts
index 744b5fdcf..a0966816e 100644
--- a/apps/clock/apps/web/src/lib/content/help/index.ts
+++ b/apps/clock/apps/web/src/lib/content/help/index.ts
@@ -3,6 +3,7 @@
*/
import type { HelpContent } from '@manacore/shared-help-types';
+import { getPrivacyFAQs } from '@manacore/shared-help-types';
export function getClockHelpContent(locale: string): HelpContent {
const isDE = locale === 'de';
@@ -59,18 +60,16 @@ export function getClockHelpContent(locale: string): HelpContent {
? ['life-clock', 'lebenszeit', 'visualisierung']
: ['life-clock', 'lifetime', 'visualization'],
},
- {
- id: 'faq-privacy',
- question: isDE ? 'Wie werden meine Daten geschützt?' : 'How is my data protected?',
- answer: isDE
- ? 'Deine Daten sind sicher:
- Lokale Speicherung: Wecker und Timer werden lokal auf deinem Gerät gespeichert
- Verschlüsselung: Alle synchronisierten Daten werden bei der Übertragung (TLS) verschlüsselt
- DSGVO-konform: Wir halten uns an die EU-Datenschutzverordnung
- Kein Tracking: Deine Nutzungsdaten werden nicht an Dritte weitergegeben
'
- : 'Your data is secure:
- Local storage: Alarms and timers are stored locally on your device
- Encryption: All synced data is encrypted in transit (TLS)
- GDPR compliant: We follow EU data protection regulations
- No tracking: Your usage data is never shared with third parties
',
- category: 'privacy',
- order: 5,
- language: isDE ? 'de' : 'en',
- featured: true,
- tags: isDE ? ['datenschutz', 'dsgvo', 'sicherheit'] : ['privacy', 'gdpr', 'security'],
- },
+ ...getPrivacyFAQs(locale, {
+ dataTypeDE: 'Daten',
+ dataTypeEN: 'data',
+ extraBulletsDE: [
+ 'Lokale Speicherung: Wecker und Timer werden lokal auf deinem Gerät gespeichert',
+ ],
+ extraBulletsEN: [
+ 'Local storage: Alarms and timers are stored locally on your device',
+ ],
+ }),
],
features: [
{
diff --git a/apps/contacts/apps/web/src/lib/content/help/index.test.ts b/apps/contacts/apps/web/src/lib/content/help/index.test.ts
new file mode 100644
index 000000000..47af5635a
--- /dev/null
+++ b/apps/contacts/apps/web/src/lib/content/help/index.test.ts
@@ -0,0 +1,47 @@
+import { describe, it, expect } from 'vitest';
+import { getContactsHelpContent } from './index';
+
+describe('Contacts Help Content', () => {
+ it('returns valid German content', () => {
+ const content = getContactsHelpContent('de');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ expect(content.contact.supportEmail).toBe('support@mana.how');
+ });
+
+ it('returns valid English content', () => {
+ const content = getContactsHelpContent('en');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ });
+
+ it('returns same number of FAQ items for both languages', () => {
+ const de = getContactsHelpContent('de');
+ const en = getContactsHelpContent('en');
+
+ expect(de.faq.length).toBe(en.faq.length);
+ expect(de.features.length).toBe(en.features.length);
+ });
+
+ it('has unique FAQ IDs', () => {
+ const content = getContactsHelpContent('de');
+ const ids = content.faq.map((f) => f.id);
+ expect(new Set(ids).size).toBe(ids.length);
+ });
+});
diff --git a/apps/contacts/apps/web/src/lib/content/help/index.ts b/apps/contacts/apps/web/src/lib/content/help/index.ts
index 0e195a463..ae2d436ca 100644
--- a/apps/contacts/apps/web/src/lib/content/help/index.ts
+++ b/apps/contacts/apps/web/src/lib/content/help/index.ts
@@ -4,6 +4,7 @@
*/
import type { HelpContent } from '@manacore/shared-help-types';
+import { getPrivacyFAQs } from '@manacore/shared-help-types';
export function getContactsHelpContent(locale: string): HelpContent {
const isDE = locale === 'de';
@@ -56,18 +57,7 @@ export function getContactsHelpContent(locale: string): HelpContent {
language: isDE ? 'de' : 'en',
tags: isDE ? ['abo', 'kündigung', 'abrechnung'] : ['subscription', 'cancel', 'billing'],
},
- {
- id: 'faq-privacy',
- question: isDE ? 'Wie werden meine Daten geschützt?' : 'How is my data protected?',
- answer: isDE
- ? 'Wir nehmen deinen Datenschutz ernst:
- Verschlüsselung: Alle Daten werden bei der Übertragung (TLS) und im Ruhezustand verschlüsselt
- DSGVO-konform: Wir halten uns an die EU-Datenschutzverordnung
- Kein Datenverkauf: Wir verkaufen niemals deine persönlichen Daten
- Datenexport: Du kannst jederzeit alle deine Daten exportieren
- Kontolöschung: Du kannst dein Konto und alle Daten dauerhaft löschen
'
- : 'We take your privacy seriously:
- Encryption: All data is encrypted in transit (TLS) and at rest
- GDPR Compliant: We follow EU data protection regulations
- No Data Selling: We never sell your personal data
- Data Export: You can export all your data at any time
- Account Deletion: You can permanently delete your account and all data
',
- category: 'privacy',
- order: 5,
- language: isDE ? 'de' : 'en',
- featured: true,
- tags: isDE ? ['datenschutz', 'dsgvo', 'sicherheit'] : ['privacy', 'gdpr', 'security'],
- },
+ ...getPrivacyFAQs(locale, { dataTypeDE: 'Kontakte', dataTypeEN: 'contacts' }),
],
features: [
{
diff --git a/apps/context/apps/web/src/lib/content/help/index.test.ts b/apps/context/apps/web/src/lib/content/help/index.test.ts
new file mode 100644
index 000000000..bce7c2eac
--- /dev/null
+++ b/apps/context/apps/web/src/lib/content/help/index.test.ts
@@ -0,0 +1,47 @@
+import { describe, it, expect } from 'vitest';
+import { getContextHelpContent } from './index';
+
+describe('Context Help Content', () => {
+ it('returns valid German content', () => {
+ const content = getContextHelpContent('de');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ expect(content.contact.supportEmail).toBe('support@mana.how');
+ });
+
+ it('returns valid English content', () => {
+ const content = getContextHelpContent('en');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ });
+
+ it('returns same number of FAQ items for both languages', () => {
+ const de = getContextHelpContent('de');
+ const en = getContextHelpContent('en');
+
+ expect(de.faq.length).toBe(en.faq.length);
+ expect(de.features.length).toBe(en.features.length);
+ });
+
+ it('has unique FAQ IDs', () => {
+ const content = getContextHelpContent('de');
+ const ids = content.faq.map((f) => f.id);
+ expect(new Set(ids).size).toBe(ids.length);
+ });
+});
diff --git a/apps/context/apps/web/src/lib/content/help/index.ts b/apps/context/apps/web/src/lib/content/help/index.ts
index 32bc9f7a5..1f5473fc2 100644
--- a/apps/context/apps/web/src/lib/content/help/index.ts
+++ b/apps/context/apps/web/src/lib/content/help/index.ts
@@ -3,6 +3,7 @@
*/
import type { HelpContent } from '@manacore/shared-help-types';
+import { getPrivacyFAQs } from '@manacore/shared-help-types';
export function getContextHelpContent(locale: string): HelpContent {
const isDE = locale === 'de';
@@ -59,18 +60,7 @@ export function getContextHelpContent(locale: string): HelpContent {
? ['versionierung', 'historie', 'wiederherstellen']
: ['versioning', 'history', 'restore'],
},
- {
- id: 'faq-privacy',
- question: isDE ? 'Wie werden meine Daten geschützt?' : 'How is my data protected?',
- answer: isDE
- ? 'Deine Daten sind sicher:
- Verschlüsselung: Alle Daten werden bei der Übertragung (TLS) und im Ruhezustand verschlüsselt
- DSGVO-konform: Wir halten uns an die EU-Datenschutzverordnung
- KI-Datenschutz: Deine Dokumente werden nicht zum Trainieren von KI-Modellen verwendet
- Datenexport: Du kannst jederzeit alle Dokumente exportieren
'
- : 'Your data is secure:
- Encryption: All data is encrypted in transit (TLS) and at rest
- GDPR compliant: We follow EU data protection regulations
- AI privacy: Your documents are not used to train AI models
- Data export: You can export all documents at any time
',
- category: 'privacy',
- order: 5,
- language: isDE ? 'de' : 'en',
- featured: true,
- tags: isDE ? ['datenschutz', 'dsgvo', 'sicherheit'] : ['privacy', 'gdpr', 'security'],
- },
+ ...getPrivacyFAQs(locale, { dataTypeDE: 'Dokumente', dataTypeEN: 'documents' }),
],
features: [
{
diff --git a/apps/manacore/apps/web/src/lib/content/help/index.test.ts b/apps/manacore/apps/web/src/lib/content/help/index.test.ts
new file mode 100644
index 000000000..80c9ac42d
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/content/help/index.test.ts
@@ -0,0 +1,47 @@
+import { describe, it, expect } from 'vitest';
+import { getManaCoreHelpContent } from './index';
+
+describe('ManaCore Help Content', () => {
+ it('returns valid German content', () => {
+ const content = getManaCoreHelpContent('de');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ expect(content.contact.supportEmail).toBe('support@mana.how');
+ });
+
+ it('returns valid English content', () => {
+ const content = getManaCoreHelpContent('en');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ });
+
+ it('returns same number of FAQ items for both languages', () => {
+ const de = getManaCoreHelpContent('de');
+ const en = getManaCoreHelpContent('en');
+
+ expect(de.faq.length).toBe(en.faq.length);
+ expect(de.features.length).toBe(en.features.length);
+ });
+
+ it('has unique FAQ IDs', () => {
+ const content = getManaCoreHelpContent('de');
+ const ids = content.faq.map((f) => f.id);
+ expect(new Set(ids).size).toBe(ids.length);
+ });
+});
diff --git a/apps/manacore/apps/web/src/lib/content/help/index.ts b/apps/manacore/apps/web/src/lib/content/help/index.ts
index a4e65b8ef..eb312807f 100644
--- a/apps/manacore/apps/web/src/lib/content/help/index.ts
+++ b/apps/manacore/apps/web/src/lib/content/help/index.ts
@@ -3,6 +3,7 @@
*/
import type { HelpContent } from '@manacore/shared-help-types';
+import { getPrivacyFAQs } from '@manacore/shared-help-types';
export function getManaCoreHelpContent(locale: string): HelpContent {
const isDE = locale === 'de';
@@ -59,18 +60,7 @@ export function getManaCoreHelpContent(locale: string): HelpContent {
language: isDE ? 'de' : 'en',
tags: isDE ? ['wechseln', 'apps', 'navigation'] : ['switch', 'apps', 'navigation'],
},
- {
- id: 'faq-privacy',
- question: isDE ? 'Wie werden meine Daten geschützt?' : 'How is my data protected?',
- answer: isDE
- ? 'Deine Daten sind sicher:
- Verschlüsselung: Alle Daten werden bei der Übertragung (TLS) und im Ruhezustand verschlüsselt
- DSGVO-konform: Wir halten uns an die EU-Datenschutzverordnung
- Datenhoheit: Deine Daten gehören dir — exportiere oder lösche sie jederzeit
- Kein Datenverkauf: Deine Daten werden nie an Dritte verkauft
- Self-Hosted: ManaCore läuft auf unserer eigenen Infrastruktur
'
- : 'Your data is secure:
- Encryption: All data is encrypted in transit (TLS) and at rest
- GDPR compliant: We follow EU data protection regulations
- Data sovereignty: Your data belongs to you — export or delete it anytime
- No data selling: Your data is never sold to third parties
- Self-hosted: ManaCore runs on our own infrastructure
',
- category: 'privacy',
- order: 5,
- language: isDE ? 'de' : 'en',
- featured: true,
- tags: isDE ? ['datenschutz', 'dsgvo', 'sicherheit'] : ['privacy', 'gdpr', 'security'],
- },
+ ...getPrivacyFAQs(locale, { dataTypeDE: 'Daten', dataTypeEN: 'data' }),
],
features: [
{
diff --git a/apps/manadeck/apps/web/src/lib/content/help/index.test.ts b/apps/manadeck/apps/web/src/lib/content/help/index.test.ts
new file mode 100644
index 000000000..0220ff0b5
--- /dev/null
+++ b/apps/manadeck/apps/web/src/lib/content/help/index.test.ts
@@ -0,0 +1,47 @@
+import { describe, it, expect } from 'vitest';
+import { getManaDeckHelpContent } from './index';
+
+describe('ManaDeck Help Content', () => {
+ it('returns valid German content', () => {
+ const content = getManaDeckHelpContent('de');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ expect(content.contact.supportEmail).toBe('support@mana.how');
+ });
+
+ it('returns valid English content', () => {
+ const content = getManaDeckHelpContent('en');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ });
+
+ it('returns same number of FAQ items for both languages', () => {
+ const de = getManaDeckHelpContent('de');
+ const en = getManaDeckHelpContent('en');
+
+ expect(de.faq.length).toBe(en.faq.length);
+ expect(de.features.length).toBe(en.features.length);
+ });
+
+ it('has unique FAQ IDs', () => {
+ const content = getManaDeckHelpContent('de');
+ const ids = content.faq.map((f) => f.id);
+ expect(new Set(ids).size).toBe(ids.length);
+ });
+});
diff --git a/apps/manadeck/apps/web/src/lib/content/help/index.ts b/apps/manadeck/apps/web/src/lib/content/help/index.ts
index c91bdd772..be41d8132 100644
--- a/apps/manadeck/apps/web/src/lib/content/help/index.ts
+++ b/apps/manadeck/apps/web/src/lib/content/help/index.ts
@@ -3,6 +3,7 @@
*/
import type { HelpContent } from '@manacore/shared-help-types';
+import { getPrivacyFAQs } from '@manacore/shared-help-types';
export function getManaDeckHelpContent(locale: string): HelpContent {
const isDE = locale === 'de';
@@ -57,18 +58,7 @@ export function getManaDeckHelpContent(locale: string): HelpContent {
language: isDE ? 'de' : 'en',
tags: isDE ? ['import', 'export', 'csv', 'anki'] : ['import', 'export', 'csv', 'anki'],
},
- {
- id: 'faq-privacy',
- question: isDE ? 'Wie werden meine Daten geschützt?' : 'How is my data protected?',
- answer: isDE
- ? 'Deine Daten sind sicher:
- Verschlüsselung: Alle Daten werden bei der Übertragung (TLS) verschlüsselt
- DSGVO-konform: Wir halten uns an die EU-Datenschutzverordnung
- Kein Datenverkauf: Deine Lernkarten und Fortschritte werden nie an Dritte verkauft
- Datenexport: Du kannst jederzeit alle Decks und Karten exportieren
'
- : 'Your data is secure:
- Encryption: All data is encrypted in transit (TLS)
- GDPR compliant: We follow EU data protection regulations
- No data selling: Your flashcards and progress are never sold to third parties
- Data export: You can export all decks and cards at any time
',
- category: 'privacy',
- order: 5,
- language: isDE ? 'de' : 'en',
- featured: true,
- tags: isDE ? ['datenschutz', 'dsgvo', 'sicherheit'] : ['privacy', 'gdpr', 'security'],
- },
+ ...getPrivacyFAQs(locale, { dataTypeDE: 'Karten', dataTypeEN: 'cards' }),
],
features: [
{
diff --git a/apps/matrix/apps/web/src/lib/content/help/index.test.ts b/apps/matrix/apps/web/src/lib/content/help/index.test.ts
new file mode 100644
index 000000000..dd2e0830d
--- /dev/null
+++ b/apps/matrix/apps/web/src/lib/content/help/index.test.ts
@@ -0,0 +1,47 @@
+import { describe, it, expect } from 'vitest';
+import { getManalinkHelpContent } from './index';
+
+describe('Manalink Help Content', () => {
+ it('returns valid German content', () => {
+ const content = getManalinkHelpContent('de');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ expect(content.contact.supportEmail).toBe('support@mana.how');
+ });
+
+ it('returns valid English content', () => {
+ const content = getManalinkHelpContent('en');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ });
+
+ it('returns same number of FAQ items for both languages', () => {
+ const de = getManalinkHelpContent('de');
+ const en = getManalinkHelpContent('en');
+
+ expect(de.faq.length).toBe(en.faq.length);
+ expect(de.features.length).toBe(en.features.length);
+ });
+
+ it('has unique FAQ IDs', () => {
+ const content = getManalinkHelpContent('de');
+ const ids = content.faq.map((f) => f.id);
+ expect(new Set(ids).size).toBe(ids.length);
+ });
+});
diff --git a/apps/matrix/apps/web/src/lib/content/help/index.ts b/apps/matrix/apps/web/src/lib/content/help/index.ts
new file mode 100644
index 000000000..bb7b67763
--- /dev/null
+++ b/apps/matrix/apps/web/src/lib/content/help/index.ts
@@ -0,0 +1,276 @@
+/**
+ * Help content for Manalink (Matrix) app
+ */
+
+import type { HelpContent } from '@manacore/shared-help-types';
+import { getPrivacyFAQs } from '@manacore/shared-help-types';
+
+export function getManalinkHelpContent(locale: string): HelpContent {
+ const isDE = locale === 'de';
+
+ return {
+ faq: [
+ {
+ id: 'faq-what-is-matrix',
+ question: isDE ? 'Was ist Matrix und Manalink?' : 'What is Matrix and Manalink?',
+ answer: isDE
+ ? 'Manalink ist ein sicherer Messenger auf Basis des Matrix-Protokolls — einem dezentralen, offenen Standard für Kommunikation:
- Ende-zu-Ende-verschlüsselt
- Dezentral — kein einzelner Server kontrolliert deine Daten
- Kompatibel mit anderen Matrix-Clients (Element, FluffyChat, etc.)
- Der Standard-Homeserver ist
matrix.mana.how
'
+ : 'Manalink is a secure messenger based on the Matrix protocol — a decentralized, open standard for communication:
- End-to-end encrypted
- Decentralized — no single server controls your data
- Compatible with other Matrix clients (Element, FluffyChat, etc.)
- The default homeserver is
matrix.mana.how
',
+ category: 'features',
+ order: 1,
+ language: isDE ? 'de' : 'en',
+ tags: isDE ? ['matrix', 'protokoll', 'dezentral'] : ['matrix', 'protocol', 'decentralized'],
+ },
+ {
+ id: 'faq-login',
+ question: isDE ? 'Wie melde ich mich an?' : 'How do I log in?',
+ answer: isDE
+ ? 'Du kannst dich auf zwei Wegen anmelden:
- Matrix-Konto — Benutzername und Passwort eines Matrix-Homeservers
- SSO über Mana Core — Anmeldung mit deinem ManaCore-Konto
Der Standard-Homeserver ist matrix.mana.how, aber du kannst jeden beliebigen Matrix-Homeserver verwenden.
'
+ : 'You can log in two ways:
- Matrix account — Username and password from a Matrix homeserver
- SSO via Mana Core — Login with your ManaCore account
The default homeserver is matrix.mana.how, but you can use any Matrix homeserver.
',
+ category: 'getting-started',
+ order: 2,
+ language: isDE ? 'de' : 'en',
+ tags: isDE ? ['anmeldung', 'login', 'sso'] : ['login', 'auth', 'sso'],
+ },
+ {
+ id: 'faq-rooms',
+ question: isDE ? 'Wie funktionieren Räume?' : 'How do rooms work?',
+ answer: isDE
+ ? 'Räume sind Chatgruppen in Matrix:
- Direktnachrichten — 1:1 Gespräche
- Gruppenräume — Mehrere Teilnehmer
- Du kannst Räume erstellen, beitreten und verwalten
- Räume zeigen ungelesene Nachrichten und Highlight-Zähler an
'
+ : 'Rooms are chat groups in Matrix:
- Direct messages — 1:1 conversations
- Group rooms — Multiple participants
- You can create, join, and manage rooms
- Rooms show unread message and highlight counters
',
+ category: 'features',
+ order: 3,
+ language: isDE ? 'de' : 'en',
+ tags: isDE ? ['räume', 'chat', 'gruppen'] : ['rooms', 'chat', 'groups'],
+ },
+ {
+ id: 'faq-messaging',
+ question: isDE
+ ? 'Welche Nachrichtenfunktionen gibt es?'
+ : 'What messaging features are available?',
+ answer: isDE
+ ? 'Manalink bietet umfangreiche Nachrichtenfunktionen:
- Textnachrichten senden und empfangen
- Tipp-Indikatoren — Sieh, wenn jemand gerade tippt
- Lesebestätigungen — Wisse, wann deine Nachricht gelesen wurde
- Nachrichtensuche — Durchsuche den Chatverlauf
- Paginierung — Lade ältere Nachrichten nach
'
+ : 'Manalink offers comprehensive messaging features:
- Text messages — send and receive
- Typing indicators — See when someone is typing
- Read receipts — Know when your message was read
- Message search — Search through chat history
- Pagination — Load older messages
',
+ category: 'features',
+ order: 4,
+ language: isDE ? 'de' : 'en',
+ tags: isDE
+ ? ['nachrichten', 'tippen', 'lesen', 'suche']
+ : ['messages', 'typing', 'read', 'search'],
+ },
+ {
+ id: 'faq-encryption',
+ question: isDE ? 'Sind meine Nachrichten verschlüsselt?' : 'Are my messages encrypted?',
+ answer: isDE
+ ? 'Ja, Manalink unterstützt Ende-zu-Ende-Verschlüsselung (E2EE) über das Matrix-Protokoll:
- Verschlüsselte Räume sind nur für die Teilnehmer lesbar
- Nicht einmal der Server kann die Nachrichten lesen
- Die Verschlüsselung verwendet bewährte kryptographische Verfahren (Olm/Megolm)
'
+ : 'Yes, Manalink supports end-to-end encryption (E2EE) via the Matrix protocol:
- Encrypted rooms are only readable by participants
- Not even the server can read the messages
- Encryption uses proven cryptographic methods (Olm/Megolm)
',
+ category: 'privacy',
+ order: 5,
+ language: isDE ? 'de' : 'en',
+ tags: isDE ? ['verschlüsselung', 'e2ee', 'sicherheit'] : ['encryption', 'e2ee', 'security'],
+ },
+ {
+ id: 'faq-room-settings',
+ question: isDE ? 'Wie verwalte ich Raumeinstellungen?' : 'How do I manage room settings?',
+ answer: isDE
+ ? 'Jeder Raum hat eigene Einstellungen:
- Name und Thema des Raums ändern
- Mitglieder einladen und verwalten
- Benachrichtigungen pro Raum konfigurieren
'
+ : 'Each room has its own settings:
- Change the name and topic of the room
- Invite and manage members
- Configure notifications per room
',
+ category: 'features',
+ order: 6,
+ language: isDE ? 'de' : 'en',
+ tags: isDE ? ['einstellungen', 'raum', 'mitglieder'] : ['settings', 'room', 'members'],
+ },
+ {
+ id: 'faq-bots',
+ question: isDE ? 'Was sind Bots?' : 'What are bots?',
+ answer: isDE
+ ? 'Unter Bots findest du automatisierte Assistenten, die in Räumen helfen können. Bots können Aufgaben automatisieren, Informationen bereitstellen und den Chat bereichern.
'
+ : 'Under Bots you can find automated assistants that can help in rooms. Bots can automate tasks, provide information, and enrich the chat experience.
',
+ category: 'features',
+ order: 7,
+ language: isDE ? 'de' : 'en',
+ tags: isDE
+ ? ['bots', 'automatisierung', 'assistenten']
+ : ['bots', 'automation', 'assistants'],
+ },
+ {
+ id: 'faq-pwa',
+ question: isDE
+ ? 'Kann ich Manalink auf dem Handy nutzen?'
+ : 'Can I use Manalink on my phone?',
+ answer: isDE
+ ? 'Ja! Manalink ist eine Progressive Web App (PWA):
- Öffne die App im Browser und tippe auf "Zum Startbildschirm hinzufügen"
- Funktioniert auch offline dank Service Worker Caching
- Push-Benachrichtigungen für neue Nachrichten
- Vollbild-App-Erfahrung ohne Browser-Leiste
'
+ : 'Yes! Manalink is a Progressive Web App (PWA):
- Open the app in your browser and tap "Add to Home Screen"
- Works offline thanks to service worker caching
- Push notifications for new messages
- Fullscreen app experience without browser bar
',
+ category: 'getting-started',
+ order: 8,
+ language: isDE ? 'de' : 'en',
+ tags: isDE ? ['pwa', 'mobil', 'installieren'] : ['pwa', 'mobile', 'install'],
+ },
+ {
+ id: 'faq-feedback',
+ question: isDE ? 'Wie kann ich Feedback geben?' : 'How can I give feedback?',
+ answer: isDE
+ ? 'Dein Feedback hilft uns, Manalink zu verbessern:
- Nutze die Feedback-Seite im Menü, um Verbesserungsvorschläge, Fehlermeldungen oder Feature-Wünsche einzureichen
- Wir lesen jedes Feedback und arbeiten kontinuierlich an Verbesserungen
'
+ : 'Your feedback helps us improve Manalink:
- Use the Feedback page in the menu to submit improvement suggestions, bug reports, or feature requests
- We read every piece of feedback and continuously work on improvements
',
+ category: 'general',
+ order: 9,
+ language: isDE ? 'de' : 'en',
+ tags: isDE
+ ? ['feedback', 'verbesserung', 'kontakt']
+ : ['feedback', 'improvement', 'contact'],
+ },
+ ...getPrivacyFAQs(locale, {
+ dataTypeDE: 'Nachrichten',
+ dataTypeEN: 'messages',
+ extraBulletsDE: [
+ 'Ende-zu-Ende-Verschlüsselung: Verschlüsselte Räume sind nur für Teilnehmer lesbar — nicht einmal der Server kann mitlesen',
+ ],
+ extraBulletsEN: [
+ 'End-to-end encryption: Encrypted rooms are only readable by participants — not even the server can read them',
+ ],
+ }),
+ ],
+ features: [
+ {
+ id: 'feature-messaging',
+ title: isDE ? 'Sichere Nachrichten' : 'Secure Messaging',
+ description: isDE
+ ? 'Ende-zu-Ende-verschlüsselte Nachrichten über das dezentrale Matrix-Protokoll'
+ : 'End-to-end encrypted messaging via the decentralized Matrix protocol',
+ icon: '🔒',
+ category: 'core',
+ highlights: isDE
+ ? [
+ 'E2E-Verschlüsselung',
+ 'Direktnachrichten & Gruppen',
+ 'Lesebestätigungen',
+ 'Tipp-Indikatoren',
+ ]
+ : ['E2E encryption', 'Direct messages & groups', 'Read receipts', 'Typing indicators'],
+ content: '',
+ order: 1,
+ language: isDE ? 'de' : 'en',
+ },
+ {
+ id: 'feature-rooms',
+ title: isDE ? 'Räume' : 'Rooms',
+ description: isDE
+ ? 'Erstelle und verwalte Räume für Direktnachrichten und Gruppen'
+ : 'Create and manage rooms for direct messages and groups',
+ icon: '💬',
+ category: 'core',
+ highlights: isDE
+ ? ['Raum erstellen', 'Raumeinstellungen', 'Mitgliederverwaltung', 'Ungelesen-Zähler']
+ : ['Create rooms', 'Room settings', 'Member management', 'Unread counter'],
+ content: '',
+ order: 2,
+ language: isDE ? 'de' : 'en',
+ },
+ {
+ id: 'feature-search',
+ title: isDE ? 'Nachrichtensuche' : 'Message Search',
+ description: isDE
+ ? 'Durchsuche den Chatverlauf nach Nachrichten und Inhalten'
+ : 'Search through chat history for messages and content',
+ icon: '🔍',
+ category: 'core',
+ highlights: isDE
+ ? ['Volltextsuche', 'Raum-übergreifend', 'Schnelle Ergebnisse']
+ : ['Full-text search', 'Cross-room', 'Quick results'],
+ content: '',
+ order: 3,
+ language: isDE ? 'de' : 'en',
+ },
+ {
+ id: 'feature-sso',
+ title: isDE ? 'SSO-Anmeldung' : 'SSO Login',
+ description: isDE
+ ? 'Melde dich mit deinem ManaCore-Konto an — kein separates Matrix-Passwort nötig'
+ : 'Sign in with your ManaCore account — no separate Matrix password needed',
+ icon: '🔐',
+ category: 'core',
+ highlights: isDE
+ ? ['ManaCore SSO', 'Ein-Klick-Login', 'Sicher', 'Passwort-Login']
+ : ['ManaCore SSO', 'One-click login', 'Secure', 'Password login'],
+ content: '',
+ order: 4,
+ language: isDE ? 'de' : 'en',
+ },
+ {
+ id: 'feature-bots',
+ title: 'Bots',
+ description: isDE
+ ? 'Automatisierte Assistenten für Aufgaben und Informationen in Räumen'
+ : 'Automated assistants for tasks and information in rooms',
+ icon: '🤖',
+ category: 'advanced',
+ highlights: isDE
+ ? ['Chat-Assistenten', 'Automatisierung', 'Informationsdienste']
+ : ['Chat assistants', 'Automation', 'Information services'],
+ content: '',
+ order: 5,
+ language: isDE ? 'de' : 'en',
+ },
+ {
+ id: 'feature-pwa',
+ title: isDE ? 'Progressive Web App' : 'Progressive Web App',
+ description: isDE
+ ? 'Installierbar auf jedem Gerät — offline-fähig und mit Push-Benachrichtigungen'
+ : 'Installable on any device — works offline and supports push notifications',
+ icon: '📱',
+ category: 'core',
+ highlights: isDE
+ ? ['Installierbar', 'Offline-fähig', 'Push-Benachrichtigungen', 'Vollbild']
+ : ['Installable', 'Works offline', 'Push notifications', 'Fullscreen'],
+ content: '',
+ order: 6,
+ language: isDE ? 'de' : 'en',
+ },
+ {
+ id: 'feature-federation',
+ title: isDE ? 'Föderation' : 'Federation',
+ description: isDE
+ ? 'Kommuniziere mit Nutzern auf anderen Matrix-Homeservern'
+ : 'Communicate with users on other Matrix homeservers',
+ icon: '🌐',
+ category: 'advanced',
+ highlights: isDE
+ ? ['Server-übergreifend', 'Offenes Protokoll', 'Element-kompatibel']
+ : ['Cross-server', 'Open protocol', 'Element-compatible'],
+ content: '',
+ order: 7,
+ language: isDE ? 'de' : 'en',
+ },
+ {
+ id: 'feature-settings',
+ title: isDE ? 'Einstellungen' : 'Settings',
+ description: isDE
+ ? 'Passe Manalink an — Themes, Benachrichtigungen und Kontoeinstellungen'
+ : 'Customize Manalink — themes, notifications, and account settings',
+ icon: '⚙️',
+ category: 'core',
+ highlights: isDE
+ ? ['Hell/Dunkel-Modus', 'Benachrichtigungen', 'Kontoeinstellungen']
+ : ['Light/Dark mode', 'Notifications', 'Account settings'],
+ content: '',
+ order: 8,
+ language: isDE ? 'de' : 'en',
+ },
+ ],
+ shortcuts: [],
+ gettingStarted: [],
+ changelog: [],
+ contact: {
+ id: 'contact-support',
+ title: isDE ? 'Support kontaktieren' : 'Contact Support',
+ content: isDE
+ ? 'Unser Support-Team hilft dir bei allen Fragen rund um Manalink. Nutze auch die Feedback-Funktion im Menü, um uns direkt Verbesserungsvorschläge zu schicken.
'
+ : 'Our support team is here to help you with any questions about Manalink. You can also use the feedback feature in the menu to send us improvement suggestions directly.
',
+ language: isDE ? 'de' : 'en',
+ order: 1,
+ supportEmail: 'support@mana.how',
+ documentationUrl: 'https://mana.how/docs',
+ responseTime: isDE ? 'Normalerweise innerhalb von 24 Stunden' : 'Usually within 24 hours',
+ },
+ };
+}
diff --git a/apps/mukke/apps/web/src/lib/content/help/index.test.ts b/apps/mukke/apps/web/src/lib/content/help/index.test.ts
new file mode 100644
index 000000000..15629599a
--- /dev/null
+++ b/apps/mukke/apps/web/src/lib/content/help/index.test.ts
@@ -0,0 +1,47 @@
+import { describe, it, expect } from 'vitest';
+import { getMukkeHelpContent } from './index';
+
+describe('Mukke Help Content', () => {
+ it('returns valid German content', () => {
+ const content = getMukkeHelpContent('de');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ expect(content.contact.supportEmail).toBe('support@mana.how');
+ });
+
+ it('returns valid English content', () => {
+ const content = getMukkeHelpContent('en');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ });
+
+ it('returns same number of FAQ items for both languages', () => {
+ const de = getMukkeHelpContent('de');
+ const en = getMukkeHelpContent('en');
+
+ expect(de.faq.length).toBe(en.faq.length);
+ expect(de.features.length).toBe(en.features.length);
+ });
+
+ it('has unique FAQ IDs', () => {
+ const content = getMukkeHelpContent('de');
+ const ids = content.faq.map((f) => f.id);
+ expect(new Set(ids).size).toBe(ids.length);
+ });
+});
diff --git a/apps/mukke/apps/web/src/lib/content/help/index.ts b/apps/mukke/apps/web/src/lib/content/help/index.ts
index 31731ca79..683295149 100644
--- a/apps/mukke/apps/web/src/lib/content/help/index.ts
+++ b/apps/mukke/apps/web/src/lib/content/help/index.ts
@@ -3,6 +3,7 @@
*/
import type { HelpContent } from '@manacore/shared-help-types';
+import { getPrivacyFAQs } from '@manacore/shared-help-types';
export function getMukkeHelpContent(locale: string): HelpContent {
const isDE = locale === 'de';
@@ -47,18 +48,7 @@ export function getMukkeHelpContent(locale: string): HelpContent {
language: isDE ? 'de' : 'en',
tags: isDE ? ['playlist', 'erstellen', 'sortieren'] : ['playlist', 'create', 'sort'],
},
- {
- id: 'faq-privacy',
- question: isDE ? 'Wie werden meine Daten geschützt?' : 'How is my data protected?',
- answer: isDE
- ? 'Deine Musik ist sicher:
- Privat: Nur du hast Zugriff auf deine Bibliothek
- Verschlüsselung: Alle Daten werden bei der Übertragung (TLS) verschlüsselt
- DSGVO-konform: Wir halten uns an die EU-Datenschutzverordnung
'
- : 'Your music is secure:
- Private: Only you have access to your library
- Encryption: All data is encrypted in transit (TLS)
- GDPR Compliant: We follow EU data protection regulations
',
- category: 'privacy',
- order: 4,
- language: isDE ? 'de' : 'en',
- featured: true,
- tags: isDE ? ['datenschutz', 'dsgvo', 'sicherheit'] : ['privacy', 'gdpr', 'security'],
- },
+ ...getPrivacyFAQs(locale, { dataTypeDE: 'Musik', dataTypeEN: 'music' }),
],
features: [
{
diff --git a/apps/photos/apps/web/src/lib/content/help/index.test.ts b/apps/photos/apps/web/src/lib/content/help/index.test.ts
new file mode 100644
index 000000000..cd0cffc8b
--- /dev/null
+++ b/apps/photos/apps/web/src/lib/content/help/index.test.ts
@@ -0,0 +1,47 @@
+import { describe, it, expect } from 'vitest';
+import { getPhotosHelpContent } from './index';
+
+describe('Photos Help Content', () => {
+ it('returns valid German content', () => {
+ const content = getPhotosHelpContent('de');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ expect(content.contact.supportEmail).toBe('support@mana.how');
+ });
+
+ it('returns valid English content', () => {
+ const content = getPhotosHelpContent('en');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ });
+
+ it('returns same number of FAQ items for both languages', () => {
+ const de = getPhotosHelpContent('de');
+ const en = getPhotosHelpContent('en');
+
+ expect(de.faq.length).toBe(en.faq.length);
+ expect(de.features.length).toBe(en.features.length);
+ });
+
+ it('has unique FAQ IDs', () => {
+ const content = getPhotosHelpContent('de');
+ const ids = content.faq.map((f) => f.id);
+ expect(new Set(ids).size).toBe(ids.length);
+ });
+});
diff --git a/apps/photos/apps/web/src/lib/content/help/index.ts b/apps/photos/apps/web/src/lib/content/help/index.ts
index 7ee1a5a86..7520b8e12 100644
--- a/apps/photos/apps/web/src/lib/content/help/index.ts
+++ b/apps/photos/apps/web/src/lib/content/help/index.ts
@@ -3,6 +3,7 @@
*/
import type { HelpContent } from '@manacore/shared-help-types';
+import { getPrivacyFAQs } from '@manacore/shared-help-types';
export function getPhotosHelpContent(locale: string): HelpContent {
const isDE = locale === 'de';
@@ -53,17 +54,7 @@ export function getPhotosHelpContent(locale: string): HelpContent {
language: isDE ? 'de' : 'en',
tags: ['favorites', 'heart', 'bookmarks'],
},
- {
- id: 'faq-privacy',
- question: isDE ? 'Wie werden meine Fotos geschützt?' : 'How are my photos protected?',
- answer: isDE
- ? 'Deine Fotos sind sicher bei ManaCore:
- Alle Fotos werden verschlüsselt übertragen und gespeichert
- Nur du hast Zugriff auf deine Galerie
- Fotos werden nicht für KI-Training oder andere Zwecke verwendet
- Du kannst jederzeit Fotos löschen — sie werden dann dauerhaft entfernt
'
- : 'Your photos are safe with ManaCore:
- All photos are encrypted in transit and at rest
- Only you have access to your gallery
- Photos are not used for AI training or other purposes
- You can delete photos at any time — they are permanently removed
',
- category: 'privacy',
- order: 5,
- language: isDE ? 'de' : 'en',
- tags: ['privacy', 'security', 'data'],
- },
+ ...getPrivacyFAQs(locale, { dataTypeDE: 'Fotos', dataTypeEN: 'photos' }),
],
features: [
{
diff --git a/apps/picture/apps/web/src/lib/content/help/index.test.ts b/apps/picture/apps/web/src/lib/content/help/index.test.ts
new file mode 100644
index 000000000..9e85eac22
--- /dev/null
+++ b/apps/picture/apps/web/src/lib/content/help/index.test.ts
@@ -0,0 +1,47 @@
+import { describe, it, expect } from 'vitest';
+import { getPictureHelpContent } from './index';
+
+describe('Picture Help Content', () => {
+ it('returns valid German content', () => {
+ const content = getPictureHelpContent('de');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ expect(content.contact.supportEmail).toBe('support@mana.how');
+ });
+
+ it('returns valid English content', () => {
+ const content = getPictureHelpContent('en');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ });
+
+ it('returns same number of FAQ items for both languages', () => {
+ const de = getPictureHelpContent('de');
+ const en = getPictureHelpContent('en');
+
+ expect(de.faq.length).toBe(en.faq.length);
+ expect(de.features.length).toBe(en.features.length);
+ });
+
+ it('has unique FAQ IDs', () => {
+ const content = getPictureHelpContent('de');
+ const ids = content.faq.map((f) => f.id);
+ expect(new Set(ids).size).toBe(ids.length);
+ });
+});
diff --git a/apps/picture/apps/web/src/lib/content/help/index.ts b/apps/picture/apps/web/src/lib/content/help/index.ts
index eba973558..5485ffa0f 100644
--- a/apps/picture/apps/web/src/lib/content/help/index.ts
+++ b/apps/picture/apps/web/src/lib/content/help/index.ts
@@ -3,6 +3,7 @@
*/
import type { HelpContent } from '@manacore/shared-help-types';
+import { getPrivacyFAQs } from '@manacore/shared-help-types';
export function getPictureHelpContent(locale: string): HelpContent {
const isDE = locale === 'de';
@@ -60,18 +61,7 @@ export function getPictureHelpContent(locale: string): HelpContent {
? ['explore', 'entdecken', 'inspiration']
: ['explore', 'discover', 'inspiration'],
},
- {
- id: 'faq-privacy',
- question: isDE ? 'Wie werden meine Bilder geschützt?' : 'How are my images protected?',
- answer: isDE
- ? 'Deine Bilder sind sicher:
- Privat: Generierte Bilder sind standardmäßig nur für dich sichtbar
- Verschlüsselung: Alle Daten werden bei der Übertragung (TLS) verschlüsselt
- DSGVO-konform: Wir halten uns an die EU-Datenschutzverordnung
- Löschung: Du kannst Bilder jederzeit dauerhaft löschen
'
- : 'Your images are secure:
- Private: Generated images are only visible to you by default
- Encryption: All data is encrypted in transit (TLS)
- GDPR Compliant: We follow EU data protection regulations
- Deletion: You can permanently delete images at any time
',
- category: 'privacy',
- order: 5,
- language: isDE ? 'de' : 'en',
- featured: true,
- tags: isDE ? ['datenschutz', 'sicherheit', 'dsgvo'] : ['privacy', 'security', 'gdpr'],
- },
+ ...getPrivacyFAQs(locale, { dataTypeDE: 'Bilder', dataTypeEN: 'images' }),
],
features: [
{
diff --git a/apps/planta/apps/web/src/lib/content/help/index.test.ts b/apps/planta/apps/web/src/lib/content/help/index.test.ts
new file mode 100644
index 000000000..98945d161
--- /dev/null
+++ b/apps/planta/apps/web/src/lib/content/help/index.test.ts
@@ -0,0 +1,47 @@
+import { describe, it, expect } from 'vitest';
+import { getPlantaHelpContent } from './index';
+
+describe('Planta Help Content', () => {
+ it('returns valid German content', () => {
+ const content = getPlantaHelpContent('de');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ expect(content.contact.supportEmail).toBe('support@mana.how');
+ });
+
+ it('returns valid English content', () => {
+ const content = getPlantaHelpContent('en');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ });
+
+ it('returns same number of FAQ items for both languages', () => {
+ const de = getPlantaHelpContent('de');
+ const en = getPlantaHelpContent('en');
+
+ expect(de.faq.length).toBe(en.faq.length);
+ expect(de.features.length).toBe(en.features.length);
+ });
+
+ it('has unique FAQ IDs', () => {
+ const content = getPlantaHelpContent('de');
+ const ids = content.faq.map((f) => f.id);
+ expect(new Set(ids).size).toBe(ids.length);
+ });
+});
diff --git a/apps/planta/apps/web/src/lib/content/help/index.ts b/apps/planta/apps/web/src/lib/content/help/index.ts
index 1946de89f..40ddff67b 100644
--- a/apps/planta/apps/web/src/lib/content/help/index.ts
+++ b/apps/planta/apps/web/src/lib/content/help/index.ts
@@ -3,6 +3,7 @@
*/
import type { HelpContent } from '@manacore/shared-help-types';
+import { getPrivacyFAQs } from '@manacore/shared-help-types';
export function getPlantaHelpContent(locale: string): HelpContent {
const isDE = locale === 'de';
@@ -57,17 +58,7 @@ export function getPlantaHelpContent(locale: string): HelpContent {
language: isDE ? 'de' : 'en',
tags: ['health', 'diseases', 'pests', 'monitoring'],
},
- {
- id: 'faq-privacy',
- question: isDE ? 'Wie werden meine Daten geschützt?' : 'How is my data protected?',
- answer: isDE
- ? 'Deine Pflanzendaten sind sicher bei Planta:
- Fotos werden nur zur Pflanzenidentifizierung verwendet und nicht an Dritte weitergegeben
- Deine Pflanzensammlung ist nur für dich sichtbar
- Standortdaten werden nur lokal für Wetteranpassungen genutzt
- Du kannst jederzeit alle Daten exportieren oder löschen
'
- : 'Your plant data is safe with Planta:
- Photos are only used for plant identification and are not shared with third parties
- Your plant collection is visible only to you
- Location data is only used locally for weather adjustments
- You can export or delete all data at any time
',
- category: 'privacy',
- order: 5,
- language: isDE ? 'de' : 'en',
- tags: ['privacy', 'security', 'data', 'photos'],
- },
+ ...getPrivacyFAQs(locale, { dataTypeDE: 'Pflanzendaten', dataTypeEN: 'plant data' }),
],
features: [
{
diff --git a/apps/presi/apps/web/src/lib/content/help/index.test.ts b/apps/presi/apps/web/src/lib/content/help/index.test.ts
new file mode 100644
index 000000000..745979ace
--- /dev/null
+++ b/apps/presi/apps/web/src/lib/content/help/index.test.ts
@@ -0,0 +1,47 @@
+import { describe, it, expect } from 'vitest';
+import { getPresiHelpContent } from './index';
+
+describe('Presi Help Content', () => {
+ it('returns valid German content', () => {
+ const content = getPresiHelpContent('de');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ expect(content.contact.supportEmail).toBe('support@mana.how');
+ });
+
+ it('returns valid English content', () => {
+ const content = getPresiHelpContent('en');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ });
+
+ it('returns same number of FAQ items for both languages', () => {
+ const de = getPresiHelpContent('de');
+ const en = getPresiHelpContent('en');
+
+ expect(de.faq.length).toBe(en.faq.length);
+ expect(de.features.length).toBe(en.features.length);
+ });
+
+ it('has unique FAQ IDs', () => {
+ const content = getPresiHelpContent('de');
+ const ids = content.faq.map((f) => f.id);
+ expect(new Set(ids).size).toBe(ids.length);
+ });
+});
diff --git a/apps/presi/apps/web/src/lib/content/help/index.ts b/apps/presi/apps/web/src/lib/content/help/index.ts
index a861167b5..0b46f318f 100644
--- a/apps/presi/apps/web/src/lib/content/help/index.ts
+++ b/apps/presi/apps/web/src/lib/content/help/index.ts
@@ -3,6 +3,7 @@
*/
import type { HelpContent } from '@manacore/shared-help-types';
+import { getPrivacyFAQs } from '@manacore/shared-help-types';
export function getPresiHelpContent(locale: string): HelpContent {
const isDE = locale === 'de';
@@ -53,17 +54,7 @@ export function getPresiHelpContent(locale: string): HelpContent {
language: isDE ? 'de' : 'en',
tags: ['themes', 'design', 'customization', 'styles'],
},
- {
- id: 'faq-privacy',
- question: isDE ? 'Wie werden meine Daten geschützt?' : 'How is my data protected?',
- answer: isDE
- ? 'Deine Präsentationen sind sicher bei Presi:
- Alle Daten werden verschlüsselt übertragen und gespeichert
- Geteilte Links können jederzeit widerrufen werden
- Du bestimmst, wer Zugriff auf deine Präsentationen hat
- Gelöschte Präsentationen werden dauerhaft entfernt
'
- : 'Your presentations are safe with Presi:
- All data is encrypted in transit and at rest
- Shared links can be revoked at any time
- You control who has access to your presentations
- Deleted presentations are permanently removed
',
- category: 'privacy',
- order: 5,
- language: isDE ? 'de' : 'en',
- tags: ['privacy', 'security', 'data', 'sharing'],
- },
+ ...getPrivacyFAQs(locale, { dataTypeDE: 'Präsentationen', dataTypeEN: 'presentations' }),
],
features: [
{
diff --git a/apps/questions/apps/web/src/lib/content/help/index.test.ts b/apps/questions/apps/web/src/lib/content/help/index.test.ts
new file mode 100644
index 000000000..3662cd991
--- /dev/null
+++ b/apps/questions/apps/web/src/lib/content/help/index.test.ts
@@ -0,0 +1,47 @@
+import { describe, it, expect } from 'vitest';
+import { getQuestionsHelpContent } from './index';
+
+describe('Questions Help Content', () => {
+ it('returns valid German content', () => {
+ const content = getQuestionsHelpContent('de');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ expect(content.contact.supportEmail).toBe('support@mana.how');
+ });
+
+ it('returns valid English content', () => {
+ const content = getQuestionsHelpContent('en');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ });
+
+ it('returns same number of FAQ items for both languages', () => {
+ const de = getQuestionsHelpContent('de');
+ const en = getQuestionsHelpContent('en');
+
+ expect(de.faq.length).toBe(en.faq.length);
+ expect(de.features.length).toBe(en.features.length);
+ });
+
+ it('has unique FAQ IDs', () => {
+ const content = getQuestionsHelpContent('de');
+ const ids = content.faq.map((f) => f.id);
+ expect(new Set(ids).size).toBe(ids.length);
+ });
+});
diff --git a/apps/questions/apps/web/src/lib/content/help/index.ts b/apps/questions/apps/web/src/lib/content/help/index.ts
index 3c34b42f3..dd7d4eb0c 100644
--- a/apps/questions/apps/web/src/lib/content/help/index.ts
+++ b/apps/questions/apps/web/src/lib/content/help/index.ts
@@ -3,6 +3,7 @@
*/
import type { HelpContent } from '@manacore/shared-help-types';
+import { getPrivacyFAQs } from '@manacore/shared-help-types';
export function getQuestionsHelpContent(locale: string): HelpContent {
const isDE = locale === 'de';
@@ -53,17 +54,7 @@ export function getQuestionsHelpContent(locale: string): HelpContent {
language: isDE ? 'de' : 'en',
tags: ['collections', 'organize', 'save', 'share'],
},
- {
- id: 'faq-privacy',
- question: isDE ? 'Wie werden meine Daten geschützt?' : 'How is my data protected?',
- answer: isDE
- ? 'Deine Recherchen sind privat und sicher:
- Suchanfragen werden nicht mit Dritten geteilt
- Dein Rechercheverlauf ist nur für dich sichtbar
- Die Websuche erfolgt über unseren eigenen Suchdienst, nicht über Google
- Du kannst deinen Verlauf jederzeit löschen
'
- : 'Your research is private and secure:
- Search queries are not shared with third parties
- Your research history is visible only to you
- Web search is performed through our own search service, not through Google
- You can delete your history at any time
',
- category: 'privacy',
- order: 5,
- language: isDE ? 'de' : 'en',
- tags: ['privacy', 'security', 'data', 'history'],
- },
+ ...getPrivacyFAQs(locale, { dataTypeDE: 'Fragen', dataTypeEN: 'questions' }),
],
features: [
{
diff --git a/apps/storage/apps/web/src/lib/content/help/index.test.ts b/apps/storage/apps/web/src/lib/content/help/index.test.ts
new file mode 100644
index 000000000..bfe17bfad
--- /dev/null
+++ b/apps/storage/apps/web/src/lib/content/help/index.test.ts
@@ -0,0 +1,47 @@
+import { describe, it, expect } from 'vitest';
+import { getStorageHelpContent } from './index';
+
+describe('Storage Help Content', () => {
+ it('returns valid German content', () => {
+ const content = getStorageHelpContent('de');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ expect(content.contact.supportEmail).toBe('support@mana.how');
+ });
+
+ it('returns valid English content', () => {
+ const content = getStorageHelpContent('en');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ });
+
+ it('returns same number of FAQ items for both languages', () => {
+ const de = getStorageHelpContent('de');
+ const en = getStorageHelpContent('en');
+
+ expect(de.faq.length).toBe(en.faq.length);
+ expect(de.features.length).toBe(en.features.length);
+ });
+
+ it('has unique FAQ IDs', () => {
+ const content = getStorageHelpContent('de');
+ const ids = content.faq.map((f) => f.id);
+ expect(new Set(ids).size).toBe(ids.length);
+ });
+});
diff --git a/apps/storage/apps/web/src/lib/content/help/index.ts b/apps/storage/apps/web/src/lib/content/help/index.ts
index 162d8fa5f..53d8dbd09 100644
--- a/apps/storage/apps/web/src/lib/content/help/index.ts
+++ b/apps/storage/apps/web/src/lib/content/help/index.ts
@@ -3,6 +3,7 @@
*/
import type { HelpContent } from '@manacore/shared-help-types';
+import { getPrivacyFAQs } from '@manacore/shared-help-types';
export function getStorageHelpContent(locale: string): HelpContent {
const isDE = locale === 'de';
@@ -53,18 +54,16 @@ export function getStorageHelpContent(locale: string): HelpContent {
language: isDE ? 'de' : 'en',
tags: isDE ? ['papierkorb', 'löschen', 'wiederherstellen'] : ['trash', 'delete', 'restore'],
},
- {
- id: 'faq-privacy',
- question: isDE ? 'Wie werden meine Dateien geschützt?' : 'How are my files protected?',
- answer: isDE
- ? 'Deine Dateien sind sicher:
- Verschlüsselung: Alle Daten werden bei der Übertragung (TLS) verschlüsselt
- Privat: Nur du hast Zugriff auf deine Dateien
- DSGVO-konform: Wir halten uns an die EU-Datenschutzverordnung
- Share-Kontrolle: Du bestimmst, wer Zugriff hat (Passwort, Ablauf, Limit)
'
- : 'Your files are secure:
- Encryption: All data is encrypted in transit (TLS)
- Private: Only you have access to your files
- GDPR Compliant: We follow EU data protection regulations
- Share control: You decide who has access (password, expiry, limits)
',
- category: 'privacy',
- order: 5,
- language: isDE ? 'de' : 'en',
- featured: true,
- tags: isDE ? ['datenschutz', 'sicherheit', 'dsgvo'] : ['privacy', 'security', 'gdpr'],
- },
+ ...getPrivacyFAQs(locale, {
+ dataTypeDE: 'Dateien',
+ dataTypeEN: 'files',
+ extraBulletsDE: [
+ 'S3-Storage: Dateien werden sicher in S3-kompatiblem Speicher (MinIO) abgelegt',
+ ],
+ extraBulletsEN: [
+ 'S3 storage: Files are securely stored in S3-compatible storage (MinIO)',
+ ],
+ }),
],
features: [
{
diff --git a/apps/todo/apps/web/src/lib/content/help/index.test.ts b/apps/todo/apps/web/src/lib/content/help/index.test.ts
new file mode 100644
index 000000000..9f8fd03d8
--- /dev/null
+++ b/apps/todo/apps/web/src/lib/content/help/index.test.ts
@@ -0,0 +1,47 @@
+import { describe, it, expect } from 'vitest';
+import { getTodoHelpContent } from './index';
+
+describe('Todo Help Content', () => {
+ it('returns valid German content', () => {
+ const content = getTodoHelpContent('de');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ expect(content.contact.supportEmail).toBe('support@mana.how');
+ });
+
+ it('returns valid English content', () => {
+ const content = getTodoHelpContent('en');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ });
+
+ it('returns same number of FAQ items for both languages', () => {
+ const de = getTodoHelpContent('de');
+ const en = getTodoHelpContent('en');
+
+ expect(de.faq.length).toBe(en.faq.length);
+ expect(de.features.length).toBe(en.features.length);
+ });
+
+ it('has unique FAQ IDs', () => {
+ const content = getTodoHelpContent('de');
+ const ids = content.faq.map((f) => f.id);
+ expect(new Set(ids).size).toBe(ids.length);
+ });
+});
diff --git a/apps/todo/apps/web/src/lib/content/help/index.ts b/apps/todo/apps/web/src/lib/content/help/index.ts
index 65b3a2fa6..e0f1e7186 100644
--- a/apps/todo/apps/web/src/lib/content/help/index.ts
+++ b/apps/todo/apps/web/src/lib/content/help/index.ts
@@ -3,6 +3,7 @@
*/
import type { HelpContent } from '@manacore/shared-help-types';
+import { getPrivacyFAQs } from '@manacore/shared-help-types';
export function getTodoHelpContent(locale: string): HelpContent {
const isDE = locale === 'de';
@@ -60,18 +61,7 @@ export function getTodoHelpContent(locale: string): HelpContent {
language: isDE ? 'de' : 'en',
tags: isDE ? ['wiederkehrend', 'wiederholung', 'serie'] : ['recurring', 'repeat', 'series'],
},
- {
- id: 'faq-privacy',
- question: isDE ? 'Wie werden meine Daten geschützt?' : 'How is my data protected?',
- answer: isDE
- ? 'Deine Daten sind sicher:
- Verschlüsselung: Alle Daten werden bei der Übertragung (TLS) verschlüsselt
- DSGVO-konform: Wir halten uns an die EU-Datenschutzverordnung
- Kein Datenverkauf: Deine Aufgaben werden nie an Dritte verkauft
- Datenexport: Du kannst jederzeit alle Daten exportieren
'
- : 'Your data is secure:
- Encryption: All data is encrypted in transit (TLS)
- GDPR Compliant: We follow EU data protection regulations
- No Data Selling: Your tasks are never sold to third parties
- Data Export: You can export all your data anytime
',
- category: 'privacy',
- order: 5,
- language: isDE ? 'de' : 'en',
- featured: true,
- tags: isDE ? ['datenschutz', 'dsgvo', 'sicherheit'] : ['privacy', 'gdpr', 'security'],
- },
+ ...getPrivacyFAQs(locale, { dataTypeDE: 'Aufgaben', dataTypeEN: 'tasks' }),
],
features: [
{
diff --git a/apps/zitare/apps/web/src/lib/content/help/index.test.ts b/apps/zitare/apps/web/src/lib/content/help/index.test.ts
new file mode 100644
index 000000000..953491211
--- /dev/null
+++ b/apps/zitare/apps/web/src/lib/content/help/index.test.ts
@@ -0,0 +1,47 @@
+import { describe, it, expect } from 'vitest';
+import { getZitareHelpContent } from './index';
+
+describe('Zitare Help Content', () => {
+ it('returns valid German content', () => {
+ const content = getZitareHelpContent('de');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ expect(content.contact.supportEmail).toBe('support@mana.how');
+ });
+
+ it('returns valid English content', () => {
+ const content = getZitareHelpContent('en');
+
+ expect(content.faq.length).toBeGreaterThan(0);
+ content.faq.forEach((faq) => {
+ expect(faq.id).toBeTruthy();
+ expect(faq.question).toBeTruthy();
+ expect(faq.answer).toBeTruthy();
+ });
+
+ expect(content.features).toBeDefined();
+ expect(content.contact).toBeDefined();
+ });
+
+ it('returns same number of FAQ items for both languages', () => {
+ const de = getZitareHelpContent('de');
+ const en = getZitareHelpContent('en');
+
+ expect(de.faq.length).toBe(en.faq.length);
+ expect(de.features.length).toBe(en.features.length);
+ });
+
+ it('has unique FAQ IDs', () => {
+ const content = getZitareHelpContent('de');
+ const ids = content.faq.map((f) => f.id);
+ expect(new Set(ids).size).toBe(ids.length);
+ });
+});
diff --git a/apps/zitare/apps/web/src/lib/content/help/index.ts b/apps/zitare/apps/web/src/lib/content/help/index.ts
index 1c7b264eb..83655ca17 100644
--- a/apps/zitare/apps/web/src/lib/content/help/index.ts
+++ b/apps/zitare/apps/web/src/lib/content/help/index.ts
@@ -3,6 +3,7 @@
*/
import type { HelpContent } from '@manacore/shared-help-types';
+import { getPrivacyFAQs } from '@manacore/shared-help-types';
export function getZitareHelpContent(locale: string): HelpContent {
const isDE = locale === 'de';
@@ -42,18 +43,7 @@ export function getZitareHelpContent(locale: string): HelpContent {
language: isDE ? 'de' : 'en',
tags: isDE ? ['listen', 'sammlung', 'organisieren'] : ['lists', 'collection', 'organize'],
},
- {
- id: 'faq-privacy',
- question: isDE ? 'Wie werden meine Daten geschützt?' : 'How is my data protected?',
- answer: isDE
- ? 'Deine Daten sind sicher:
- Verschlüsselung: Alle Daten werden bei der Übertragung (TLS) verschlüsselt
- Privat: Deine Favoriten und Listen sind nur für dich sichtbar
- DSGVO-konform: Wir halten uns an die EU-Datenschutzverordnung
'
- : 'Your data is secure:
- Encryption: All data is encrypted in transit (TLS)
- Private: Your favorites and lists are only visible to you
- GDPR Compliant: We follow EU data protection regulations
',
- category: 'privacy',
- order: 4,
- language: isDE ? 'de' : 'en',
- featured: true,
- tags: isDE ? ['datenschutz', 'dsgvo', 'sicherheit'] : ['privacy', 'gdpr', 'security'],
- },
+ ...getPrivacyFAQs(locale, { dataTypeDE: 'Zitate', dataTypeEN: 'quotes' }),
],
features: [
{