Files
NewTube/src/services/i18n.service.ts
T
bruno 97e5de74f5
CI / build-and-test (push) Successful in 13m34s
feat(channel): page details chaine multi-providers avec onglets et infinite scroll
- Strategy Pattern front: ChannelProviderInterface + 6 providers (yt/dm/tw/pt/od/ru) + factory (ids longs/courts)
- ChannelContentService: cache par onglet/tri/recherche (TTL 5min), pagination nextPage
- ChannelPage: header (banniere/avatar/subs/description ...plus), tabs dynamiques par capabilities, grilles videos/shorts 9:16/playlists/live, recherche debounced, tri recent/populaire, infinite scroll IntersectionObserver, skeletons/empty/error
- Backend: GET /api/channels/:provider/:id/content (videos/shorts/playlists/live) natif par provider
2026-09-25 10:15:12 -04:00

282 lines
9.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Injectable, computed, signal } from '@angular/core';
export type LangCode = 'en' | 'fr';
// Simple key-value dictionaries. Extend as needed.
const EN: Record<string, string> = {
'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',
// 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.duration': 'Duration',
'filter.duration.any': 'Any',
'filter.duration.short': 'Short (<10m)',
'filter.duration.medium': 'Medium (10–30m)',
'filter.duration.long': 'Long (30m+)',
'filter.language': 'Language',
'filter.date': 'Date',
// 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<string, string> = {
'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',
// 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.duration': 'Durée',
'filter.duration.any': 'Toute',
'filter.duration.short': 'Courte (<10m)',
'filter.duration.medium': 'Moyenne (10–30m)',
'filter.duration.long': 'Longue (30m+)',
'filter.language': 'Langue',
'filter.date': 'Date',
// 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<string, Record<string, string>> = { en: EN, fr: FR };
private readonly defaultLang: LangCode = 'en';
// initialize from localStorage, fallback 'en'
lang = signal<LangCode>((() => {
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, any>): 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;
}
}