342 lines
12 KiB
TypeScript
342 lines
12 KiB
TypeScript
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',
|
||
'preferences.download_languages': 'Languages to download',
|
||
'preferences.download_languages_hint': 'Only enabled languages are displayed and downloaded (subtitles/transcripts). French and English by default.',
|
||
'preferences.download_languages_empty': 'Select at least one language.',
|
||
'btn.save': 'Save',
|
||
'btn.add': 'Add',
|
||
'btn.remove': 'Remove',
|
||
// Sidebar
|
||
'nav.home': 'Home',
|
||
'nav.shorts': 'Shorts',
|
||
'nav.subscriptions': 'Subscriptions',
|
||
'nav.groups': 'Groups',
|
||
'nav.you': 'You',
|
||
'nav.history': 'History',
|
||
'nav.playlists': 'Playlists',
|
||
'nav.liked': 'Liked videos',
|
||
'nav.downloads': 'Downloads',
|
||
'nav.explore': 'Explore',
|
||
'nav.providers': 'Providers',
|
||
'nav.info': 'Information',
|
||
'nav.usage': 'Usage',
|
||
'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',
|
||
'themes.showBar': 'Show the themes bar',
|
||
'themes.hideBar': 'Hide the themes bar',
|
||
// 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<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',
|
||
'preferences.download_languages': 'Langues à télécharger',
|
||
'preferences.download_languages_hint': 'Seules les langues activées sont affichées et téléchargées (sous-titres/transcriptions). Français et anglais par défaut.',
|
||
'preferences.download_languages_empty': 'Sélectionnez au moins une langue.',
|
||
'btn.save': 'Enregistrer',
|
||
'btn.add': 'Ajouter',
|
||
'btn.remove': 'Supprimer',
|
||
// Sidebar
|
||
'nav.home': 'Accueil',
|
||
'nav.shorts': 'Shorts',
|
||
'nav.subscriptions': 'Abonnements',
|
||
'nav.groups': 'Groupes',
|
||
'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',
|
||
'nav.providers': 'Fournisseurs',
|
||
'nav.info': 'Informations',
|
||
'nav.usage': 'Utilisation',
|
||
'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',
|
||
'themes.showBar': 'Afficher la bande des thèmes',
|
||
'themes.hideBar': 'Masquer la bande des thèmes',
|
||
// 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<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;
|
||
}
|
||
}
|