mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-18 02:29:41 +02:00
Move inactive projects out of active workspace: - bauntown (community website) - maerchenzauber (AI story generation) - memoro (voice memo app) - news (news aggregation) - nutriphi (nutrition tracking) - reader (reading app) - uload (URL shortener) - wisekeep (AI wisdom extraction) Update CLAUDE.md documentation: - Add presi to active projects - Document archived projects section - Update workspace configuration Archived apps can be re-activated by moving back to apps/ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
300 lines
7.9 KiB
TypeScript
300 lines
7.9 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { View, StyleSheet, Pressable, ActivityIndicator } from 'react-native';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { useTheme } from '~/features/theme/ThemeProvider';
|
|
import { useRouter } from 'expo-router';
|
|
import Text from '~/components/atoms/Text';
|
|
import Icon from '~/components/atoms/Icon';
|
|
import { getAuthenticatedClient } from '~/features/auth/lib/supabaseClient';
|
|
import colors from '~/tailwind.config.js';
|
|
|
|
interface TranslationLinksProps {
|
|
memoId: string;
|
|
memoMetadata?: any;
|
|
}
|
|
|
|
interface TranslationInfo {
|
|
memo_id: string;
|
|
target_language: string;
|
|
translated_at: string;
|
|
translator_model?: string;
|
|
}
|
|
|
|
interface OriginalInfo {
|
|
source_memo_id: string;
|
|
source_language: string;
|
|
target_language: string;
|
|
translated_at: string;
|
|
}
|
|
|
|
// Sprach-Mapping für bessere Anzeige
|
|
const LANGUAGE_NAMES: Record<string, string> = {
|
|
de: 'Deutsch',
|
|
en: 'English',
|
|
es: 'Español',
|
|
fr: 'Français',
|
|
it: 'Italiano',
|
|
pt: 'Português',
|
|
nl: 'Nederlands',
|
|
pl: 'Polski',
|
|
ru: 'Русский',
|
|
ja: '日本語',
|
|
ko: '한국어',
|
|
zh: '中文',
|
|
ar: 'العربية',
|
|
hi: 'हिन्दी',
|
|
tr: 'Türkçe',
|
|
sv: 'Svenska',
|
|
da: 'Dansk',
|
|
no: 'Norsk',
|
|
fi: 'Suomi',
|
|
cs: 'Čeština',
|
|
sk: 'Slovenčina',
|
|
hu: 'Magyar',
|
|
ro: 'Română',
|
|
bg: 'Български',
|
|
hr: 'Hrvatski',
|
|
sr: 'Српски',
|
|
sl: 'Slovenščina',
|
|
et: 'Eesti',
|
|
lv: 'Latviešu',
|
|
lt: 'Lietuvių',
|
|
mt: 'Malti',
|
|
ga: 'Gaeilge',
|
|
el: 'Ελληνικά',
|
|
uk: 'Українська',
|
|
bn: 'বাংলা',
|
|
ur: 'اردو',
|
|
fa: 'فارسی',
|
|
vi: 'Tiếng Việt',
|
|
id: 'Bahasa Indonesia',
|
|
};
|
|
|
|
const LANGUAGE_FLAGS: Record<string, string> = {
|
|
de: '🇩🇪',
|
|
en: '🇺🇸',
|
|
es: '🇪🇸',
|
|
fr: '🇫🇷',
|
|
it: '🇮🇹',
|
|
pt: '🇵🇹',
|
|
nl: '🇳🇱',
|
|
pl: '🇵🇱',
|
|
ru: '🇷🇺',
|
|
ja: '🇯🇵',
|
|
ko: '🇰🇷',
|
|
zh: '🇨🇳',
|
|
ar: '🇸🇦',
|
|
hi: '🇮🇳',
|
|
tr: '🇹🇷',
|
|
sv: '🇸🇪',
|
|
da: '🇩🇰',
|
|
no: '🇳🇴',
|
|
fi: '🇫🇮',
|
|
cs: '🇨🇿',
|
|
sk: '🇸🇰',
|
|
hu: '🇭🇺',
|
|
ro: '🇷🇴',
|
|
bg: '🇧🇬',
|
|
hr: '🇭🇷',
|
|
sr: '🇷🇸',
|
|
sl: '🇸🇮',
|
|
et: '🇪🇪',
|
|
lv: '🇱🇻',
|
|
lt: '🇱🇹',
|
|
mt: '🇲🇹',
|
|
ga: '🇮🇪',
|
|
el: '🇬🇷',
|
|
uk: '🇺🇦',
|
|
bn: '🇧🇩',
|
|
ur: '🇵🇰',
|
|
fa: '🇮🇷',
|
|
vi: '🇻🇳',
|
|
id: '🇮🇩',
|
|
};
|
|
|
|
/**
|
|
* Komponente zur Anzeige von Übersetzungsverknüpfungen
|
|
* Zeigt Links zum Original-Memo und zu verfügbaren Übersetzungen
|
|
*/
|
|
const TranslationLinks: React.FC<TranslationLinksProps> = ({ memoId, memoMetadata }) => {
|
|
const { isDark, themeVariant } = useTheme();
|
|
const { t } = useTranslation();
|
|
const router = useRouter();
|
|
|
|
const [originalMemoTitle, setOriginalMemoTitle] = useState<string>('');
|
|
const [loadingOriginal, setLoadingOriginal] = useState(false);
|
|
|
|
// Prüfe ob das aktuelle Memo eine Übersetzung ist
|
|
const translationInfo: OriginalInfo | null = memoMetadata?.translation || null;
|
|
const isTranslation = !!translationInfo;
|
|
|
|
// Hole verfügbare Übersetzungen (wenn das aktuelle Memo das Original ist)
|
|
const availableTranslations: TranslationInfo[] = memoMetadata?.translations || [];
|
|
|
|
// Lade den Titel des Original-Memos
|
|
useEffect(() => {
|
|
const loadOriginalTitle = async () => {
|
|
if (!isTranslation || !translationInfo?.source_memo_id) return;
|
|
|
|
setLoadingOriginal(true);
|
|
try {
|
|
const supabase = await getAuthenticatedClient();
|
|
const { data: originalMemo, error } = await supabase
|
|
.from('memos')
|
|
.select('title')
|
|
.eq('id', translationInfo.source_memo_id)
|
|
.single();
|
|
|
|
if (!error && originalMemo) {
|
|
setOriginalMemoTitle(originalMemo.title || t('memo.untitled', 'Ohne Titel'));
|
|
}
|
|
} catch (error) {
|
|
console.debug('Error loading original memo title:', error);
|
|
} finally {
|
|
setLoadingOriginal(false);
|
|
}
|
|
};
|
|
|
|
loadOriginalTitle();
|
|
}, [isTranslation, translationInfo?.source_memo_id, t]);
|
|
|
|
// Helper function to get language display name
|
|
const getLanguageDisplay = (langCode: string) => {
|
|
const flag = LANGUAGE_FLAGS[langCode] || '🌐';
|
|
const name = LANGUAGE_NAMES[langCode] || langCode;
|
|
return `${flag} ${name}`;
|
|
};
|
|
|
|
// Handler für Navigation zum Original
|
|
const handleNavigateToOriginal = () => {
|
|
if (translationInfo?.source_memo_id) {
|
|
router.push(`/(protected)/(memo)/${translationInfo.source_memo_id}`);
|
|
}
|
|
};
|
|
|
|
// Handler für Navigation zu Übersetzung
|
|
const handleNavigateToTranslation = (translationMemoId: string) => {
|
|
router.push(`/(protected)/(memo)/${translationMemoId}`);
|
|
};
|
|
|
|
// Primärfarbe für Links
|
|
const primaryColor = isDark
|
|
? (colors as any).theme?.extend?.colors?.dark?.[themeVariant]?.primary || '#f8d62b'
|
|
: (colors as any).theme?.extend?.colors?.[themeVariant]?.primary || '#f8d62b';
|
|
|
|
// Styles
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
backgroundColor: isDark ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.05)',
|
|
borderRadius: 12,
|
|
padding: 16,
|
|
marginBottom: 16,
|
|
},
|
|
header: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
marginBottom: 12,
|
|
},
|
|
headerText: {
|
|
fontSize: 16,
|
|
fontWeight: '600',
|
|
color: isDark ? '#FFFFFF' : '#000000',
|
|
},
|
|
linkContainer: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
paddingVertical: 8,
|
|
paddingHorizontal: 12,
|
|
backgroundColor: isDark ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.05)',
|
|
borderRadius: 8,
|
|
marginBottom: 8,
|
|
},
|
|
linkText: {
|
|
flex: 1,
|
|
fontSize: 14,
|
|
color: isDark ? '#FFFFFF' : '#000000',
|
|
},
|
|
linkSubtext: {
|
|
fontSize: 12,
|
|
color: isDark ? 'rgba(255, 255, 255, 0.6)' : 'rgba(0, 0, 0, 0.6)',
|
|
marginTop: 2,
|
|
},
|
|
chevronIcon: {
|
|
marginLeft: 8,
|
|
},
|
|
divider: {
|
|
height: 1,
|
|
backgroundColor: isDark ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)',
|
|
marginVertical: 8,
|
|
},
|
|
});
|
|
|
|
// Wenn weder Original noch Übersetzungen vorhanden sind, nichts anzeigen
|
|
if (!isTranslation && availableTranslations.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
{/* Header */}
|
|
<View style={styles.header}>
|
|
<Icon name="globe-outline" size={20} color={isDark ? '#FFFFFF' : '#000000'} />
|
|
<View style={{ width: 12 }} />
|
|
<Text style={styles.headerText}>{t('memo.translations', 'Übersetzungen')}</Text>
|
|
</View>
|
|
|
|
{/* Link zum Original (wenn das aktuelle Memo eine Übersetzung ist) */}
|
|
{isTranslation && translationInfo && (
|
|
<Pressable
|
|
style={styles.linkContainer}
|
|
onPress={handleNavigateToOriginal}
|
|
disabled={loadingOriginal}
|
|
>
|
|
<View style={{ flex: 1, flexDirection: 'row', alignItems: 'center' }}>
|
|
<Text style={styles.linkText}>
|
|
{loadingOriginal ? (
|
|
<ActivityIndicator size="small" color={primaryColor} />
|
|
) : (
|
|
`${t('memo.original_memo', 'Original')}: ${LANGUAGE_FLAGS[translationInfo.source_language] || '🌐'} ${originalMemoTitle}`
|
|
)}
|
|
</Text>
|
|
</View>
|
|
<Icon
|
|
name="chevron-forward"
|
|
size={16}
|
|
color={isDark ? 'rgba(255, 255, 255, 0.6)' : 'rgba(0, 0, 0, 0.6)'}
|
|
style={styles.chevronIcon}
|
|
/>
|
|
</Pressable>
|
|
)}
|
|
|
|
{/* Trennlinie wenn beide Bereiche vorhanden sind */}
|
|
{isTranslation && availableTranslations.length > 0 && <View style={styles.divider} />}
|
|
|
|
{/* Links zu verfügbaren Übersetzungen */}
|
|
{availableTranslations.map((translation, index) => (
|
|
<Pressable
|
|
key={`${translation.memo_id}-${index}`}
|
|
style={styles.linkContainer}
|
|
onPress={() => handleNavigateToTranslation(translation.memo_id)}
|
|
>
|
|
<View style={{ flex: 1, flexDirection: 'row', alignItems: 'center' }}>
|
|
<Text style={styles.linkText}>
|
|
{t('memo.translation_to', 'Übersetzung in')}{' '}
|
|
{LANGUAGE_FLAGS[translation.target_language] || '🌐'}{' '}
|
|
{LANGUAGE_NAMES[translation.target_language] || translation.target_language}
|
|
</Text>
|
|
</View>
|
|
<Icon
|
|
name="chevron-forward"
|
|
size={16}
|
|
color={isDark ? 'rgba(255, 255, 255, 0.6)' : 'rgba(0, 0, 0, 0.6)'}
|
|
style={styles.chevronIcon}
|
|
/>
|
|
</Pressable>
|
|
))}
|
|
</View>
|
|
);
|
|
};
|
|
|
|
export default TranslationLinks;
|