Files
NewTube/src/services/i18n.service.ts
T

342 lines
12 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',
'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;
}
}