import { Injectable, computed, signal } from '@angular/core'; export type LangCode = 'en' | 'fr'; // Simple key-value dictionaries. Extend as needed. const EN: Record = { 'app.name': 'NewTube', // Header 'search.placeholder': 'Search...', 'label.provider': 'Provider', 'label.region': 'Region', // Regions 'region.US': 'United States', 'region.FR': 'France', 'region.BR': 'Brazil', 'region.DE': 'Germany', 'region.GB': 'United Kingdom', 'region.CA': 'Canada', 'btn.edit': 'Edit', 'btn.login': 'Login', 'btn.register': 'Register', 'menu.account': 'Account', 'menu.preferences': 'Preferences', 'menu.appearance': 'Appearance', 'menu.logout': 'Logout', 'theme.black': 'Black', 'theme.dark': 'Dark', 'theme.blue': 'Blue', 'theme.light': 'Light', // Preferences 'preferences.title': 'Preferences', 'preferences.general': 'General', 'preferences.general_hint': 'Customize your experience', 'preferences.peertube_hint': 'Manage your PeerTube instances', 'preferences.add_instance_placeholder': 'e.g. example.org', 'preferences.no_instances': 'No instances yet.', 'preferences.active': 'Active', 'preferences.set_active': 'Set Active', 'label.language': 'Language', 'label.defaultProvider': 'Default Provider', 'label.theme': 'Theme', 'label.quality': 'Default Quality', 'btn.save': 'Save', 'btn.add': 'Add', 'btn.remove': 'Remove', // Sidebar 'nav.home': 'Home', 'nav.shorts': 'Shorts', 'nav.subscriptions': 'Subscriptions', 'nav.you': 'You', 'nav.history': 'History', 'nav.playlists': 'Playlists', 'nav.liked': 'Liked videos', 'nav.downloads': 'Downloads', 'nav.explore': 'Explore', 'explore.music': 'Music', 'explore.gaming': 'Gaming', 'explore.news': 'News', 'explore.sports': 'Sports', 'explore.learning': 'Learning', // Home 'home.trending': 'Trending', 'loading.more': 'Loading more...', 'search.hint': 'Type something in the search bar to get started.', 'search.noResults': 'No results found.', // Themes + Provider/Theme pages 'themes.viewAll': 'View All', 'themes.live': 'Live Streams', 'themes.recorded': 'Recorded Streams', 'themes.videos': 'Videos', 'themes.categories': 'Categories', 'themes.backToThemes': 'Back to themes', 'themes.changeProvider': 'Change provider', 'themes.title': 'Themes', 'themes.show': 'Show', 'themes.hide': 'Hide', // Common 'common.error': 'An error occurred.', 'empty.noItems': 'No items.', 'common.more': 'More', 'common.less': 'Less', // Filters 'filter.sort': 'Sort by', 'filter.sort.recent': 'Most recent', 'filter.sort.viewed': 'Most viewed', 'filter.sort.longest': 'Longest', 'filter.sort.relevance': 'Relevance', 'filter.sort.date': 'Date', 'filter.sort.views': 'Views', 'filter.duration': 'Duration', 'filter.duration.any': 'Any', 'filter.duration.short': 'Short (<10m)', 'filter.duration.medium': 'Medium (10–30m)', 'filter.duration.long': 'Long (30m+)', 'filter.type': 'Type', 'filter.type.any': 'Any', 'filter.type.video': 'Video', 'filter.type.live': 'Live', 'filter.type.recorded': 'Recorded', 'filter.type.short': 'Shorts', 'filter.type.channel': 'Channel', 'filter.language': 'Language', 'filter.language.any': 'Any', 'filter.language.en': 'English', 'filter.language.fr': 'French', 'filter.date': 'Date', 'filter.date.any': 'Any', 'filter.date.day': 'Last 24h', 'filter.date.week': 'Last week', 'filter.date.month': 'Last month', 'filter.date.year': 'Last year', // Optional per-theme labels 'theme.finance-crypto': 'Finance & Crypto', 'theme.gaming': 'Gaming', 'theme.news': 'News', 'theme.sports': 'Sports', // Downloads 'downloads.title': 'Downloads', 'downloads.refresh': 'Refresh', 'downloads.quota': 'Storage used', 'downloads.filter.all': 'All', 'downloads.filter.active': 'Active', 'downloads.filter.completed': 'Completed', 'downloads.filter.failed': 'Failed', 'downloads.empty': 'No downloads yet. Start one from a video page.', 'downloads.audioOnly': 'Audio only', 'downloads.getFile': 'Get file', 'downloads.play': 'Play', 'downloads.fileError': 'Unable to fetch the file. Please sign in again and retry.', 'downloads.back': 'Back to downloads', 'downloads.notReady': 'This download is not completed yet.', 'downloads.loadError': 'Unable to load this download.', 'downloads.retry': 'Retry', 'downloads.state.queued': 'Queued', 'downloads.state.running': 'Downloading', 'downloads.state.merging': 'Merging', 'downloads.state.completed': 'Completed', 'downloads.state.failed': 'Failed', 'downloads.state.interrupted': 'Interrupted', }; const FR: Record = { 'app.name': 'NewTube', // Header 'search.placeholder': 'Rechercher...', 'label.provider': 'Fournisseur', 'label.region': 'Région', // Régions 'region.US': 'États-Unis', 'region.FR': 'France', 'region.BR': 'Brésil', 'region.DE': 'Allemagne', 'region.GB': 'Royaume-Uni', 'region.CA': 'Canada', 'btn.edit': 'Modifier', 'btn.login': 'Connexion', 'btn.register': 'Inscription', 'menu.account': 'Compte', 'menu.preferences': 'Préférences', 'menu.appearance': 'Apparence', 'menu.logout': 'Déconnexion', 'theme.black': 'Noir', 'theme.dark': 'Sombre', 'theme.blue': 'Bleu', 'theme.light': 'Clair', // Preferences 'preferences.title': 'Préférences', 'preferences.general': 'Préférences générales', 'preferences.general_hint': 'Personnalisez votre expérience', 'preferences.peertube_hint': 'Gérez vos instances PeerTube', 'preferences.add_instance_placeholder': 'ex: example.org', 'preferences.no_instances': 'Aucune instance pour le moment.', 'preferences.active': 'Actif', 'preferences.set_active': 'Définir actif', 'label.language': 'Langue', 'label.defaultProvider': 'Fournisseur par défaut', 'label.theme': 'Thème', 'label.quality': 'Qualité par défaut', 'btn.save': 'Enregistrer', 'btn.add': 'Ajouter', 'btn.remove': 'Supprimer', // Sidebar 'nav.home': 'Accueil', 'nav.shorts': 'Shorts', 'nav.subscriptions': 'Abonnements', 'nav.you': 'Vous', 'nav.history': 'Historique', 'nav.playlists': 'Listes de lecture', 'nav.liked': 'Vidéos que vous aimez', 'nav.downloads': 'Téléchargements', 'nav.explore': 'Explorer', 'explore.music': 'Musique', 'explore.gaming': 'Jeux vidéo', 'explore.news': 'Actualités', 'explore.sports': 'Sports', 'explore.learning': 'Apprentissage', // Home 'home.trending': 'Tendances', 'loading.more': 'Chargement...', 'search.hint': 'Tapez quelque chose dans la barre de recherche pour commencer.', 'search.noResults': 'Aucun résultat.', // Themes + Provider/Theme pages 'themes.viewAll': 'Voir tout', 'themes.live': 'En direct', 'themes.recorded': 'Rediffusions', 'themes.videos': 'Vidéos', 'themes.categories': 'Catégories', 'themes.backToThemes': 'Retour aux thèmes', 'themes.changeProvider': 'Changer de provider', 'themes.title': 'Thèmes', 'themes.show': 'Afficher', 'themes.hide': 'Masquer', // Common 'common.error': 'Une erreur est survenue.', 'empty.noItems': 'Aucun élément.', 'common.more': 'Plus', 'common.less': 'Moins', // Filters 'filter.sort': 'Tri', 'filter.sort.recent': 'Plus récents', 'filter.sort.viewed': 'Plus vus', 'filter.sort.longest': 'Plus longs', 'filter.sort.relevance': 'Pertinence', 'filter.sort.date': 'Date', 'filter.sort.views': 'Vues', 'filter.duration': 'Durée', 'filter.duration.any': 'Toute', 'filter.duration.short': 'Courte (<10m)', 'filter.duration.medium': 'Moyenne (10–30m)', 'filter.duration.long': 'Longue (30m+)', 'filter.type': 'Type', 'filter.type.any': 'Tous', 'filter.type.video': 'Vidéo', 'filter.type.live': 'Direct', 'filter.type.recorded': 'Rediffusion', 'filter.type.short': 'Shorts', 'filter.type.channel': 'Chaîne', 'filter.language': 'Langue', 'filter.language.any': 'Toutes', 'filter.language.en': 'Anglais', 'filter.language.fr': 'Français', 'filter.date': 'Date', 'filter.date.any': 'Toutes', 'filter.date.day': '24 dernières h', 'filter.date.week': 'Semaine dernière', 'filter.date.month': 'Mois dernier', 'filter.date.year': 'Année dernière', // Optional per-theme labels 'theme.finance-crypto': 'Finance & Crypto', 'theme.gaming': 'Jeux vidéo', 'theme.news': 'Actualités', 'theme.sports': 'Sports', // Downloads 'downloads.title': 'Téléchargements', 'downloads.refresh': 'Rafraîchir', 'downloads.quota': 'Stockage utilisé', 'downloads.filter.all': 'Tous', 'downloads.filter.active': 'Actifs', 'downloads.filter.completed': 'Terminés', 'downloads.filter.failed': 'Échoués', 'downloads.empty': 'Aucun téléchargement. Lancez-en un depuis une page vidéo.', 'downloads.audioOnly': 'Audio seul', 'downloads.getFile': 'Obtenir le fichier', 'downloads.play': 'Lire', 'downloads.fileError': 'Fichier inaccessible. Reconnectez-vous puis réessayez.', 'downloads.back': 'Retour aux téléchargements', 'downloads.notReady': 'Ce téléchargement n’est pas encore terminé.', 'downloads.loadError': 'Impossible de charger ce téléchargement.', 'downloads.retry': 'Réessayer', 'downloads.state.queued': 'En attente', 'downloads.state.running': 'Téléchargement', 'downloads.state.merging': 'Fusion', 'downloads.state.completed': 'Terminé', 'downloads.state.failed': 'Échoué', 'downloads.state.interrupted': 'Interrompu', }; @Injectable({ providedIn: 'root' }) export class I18nService { private readonly LS_KEY = 'newtube.lang'; private readonly dictionaries: Record> = { en: EN, fr: FR }; private readonly defaultLang: LangCode = 'en'; // initialize from localStorage, fallback 'en' lang = signal((() => { try { const ls = (localStorage.getItem(this.LS_KEY) || '').toLowerCase(); return (ls === 'fr' ? 'fr' : 'en') as LangCode; } catch { return 'en'; } })()); readonly currentDict = computed(() => this.dictionaries[this.lang()] || this.dictionaries[this.defaultLang]); constructor() { try { if (typeof document !== 'undefined') { document.documentElement.setAttribute('lang', this.lang()); } } catch {} } setLanguage(l: string | null | undefined) { const v = (String(l || '').toLowerCase() === 'fr') ? 'fr' : 'en'; this.lang.set(v as LangCode); try { localStorage.setItem(this.LS_KEY, v); if (typeof document !== 'undefined') { document.documentElement.setAttribute('lang', v); } } catch {} } t(key: string, params?: Record): string { const dict = this.currentDict(); const base = dict[key] ?? this.dictionaries[this.defaultLang][key] ?? key; if (params) { return Object.keys(params).reduce((acc, k) => acc.replace(new RegExp('{' + k + '}', 'g'), String(params[k])), base); } return base; } }