Mobile i18n (nouveau système):
- lib/i18n.ts: traductions FR/EN/FA + détection locale + RTL (I18nManager)
- (tabs)/_layout.tsx: titres tabs via t()
- 50+ clés traduites par langue
Mobile fixes:
- TitleSheet: dependency array [visible] → [visible, content]
- Partage: inclut extrait contenu + lien public si publié
- Édition: message i18n 'utilisez la version web'
Web fixes:
- 28 silent catches → console.error('[silent-catch]', e)
- Web Clipper: host_permissions déjà restreints dans dist-chrome-store
177 lines
6.5 KiB
TypeScript
177 lines
6.5 KiB
TypeScript
import { getLocales } from 'expo-localization'
|
|
import { I18nManager } from 'react-native'
|
|
|
|
export type MobileLang = 'fr' | 'en' | 'fa'
|
|
|
|
const translations: Record<MobileLang, Record<string, string>> = {
|
|
fr: {
|
|
'tab.home': 'Accueil',
|
|
'tab.notebooks': 'Carnets',
|
|
'tab.revision': 'Révision',
|
|
'tab.search': 'Recherche',
|
|
'tab.profile': 'Profil',
|
|
'common.error': 'Erreur',
|
|
'common.loading': 'Chargement…',
|
|
'common.save': 'Enregistrer',
|
|
'common.delete': 'Supprimer',
|
|
'common.cancel': 'Annuler',
|
|
'common.search': 'Rechercher',
|
|
'common.settings': 'Paramètres',
|
|
'common.logout': 'Déconnexion',
|
|
'note.title': 'Titre…',
|
|
'note.contentPlaceholder': 'Contenu…',
|
|
'note.deleteTitle': 'Supprimer la note',
|
|
'note.deleteConfirm': 'Voulez-vous vraiment supprimer cette note ?',
|
|
'note.saveError': 'Erreur de sauvegarde',
|
|
'note.deleteError': 'Impossible de supprimer la note',
|
|
'note.dayNote': 'Note du jour',
|
|
'note.dayNoteError': 'Impossible de charger la note du jour.',
|
|
'note.editTitleOnly': "Le contenu enrichi ne peut pas être édité depuis mobile. Utilisez la version web pour modifier le texte.",
|
|
'note.saveTitle': 'Enregistrer le titre',
|
|
'note.saving': 'Sauvegarde…',
|
|
'auth.loginFailed': 'Connexion échouée',
|
|
'auth.googleFailed': 'Connexion Google échouée',
|
|
'auth.serverError': 'Erreur serveur',
|
|
'auth.unauthorized': 'Non autorisé',
|
|
'auth.emailPlaceholder': 'Email',
|
|
'auth.passwordPlaceholder': 'Mot de passe',
|
|
'auth.loginButton': 'Se connecter',
|
|
'auth.orGoogle': 'ou',
|
|
'revision.title': 'Révision',
|
|
'revision.empty': 'Aucune carte à réviser',
|
|
'revision.loadingError': 'Erreur de chargement',
|
|
'revision.difficult': 'Difficile',
|
|
'revision.hard': 'Dur',
|
|
'revision.good': 'Bien',
|
|
'revision.easy': 'Facile',
|
|
'profile.title': 'Profil',
|
|
'search.placeholder': 'Rechercher des notes…',
|
|
'search.noResults': 'Aucun résultat',
|
|
'ai.improve': "✨ Améliorer avec l'IA",
|
|
},
|
|
en: {
|
|
'tab.home': 'Home',
|
|
'tab.notebooks': 'Notebooks',
|
|
'tab.revision': 'Review',
|
|
'tab.search': 'Search',
|
|
'tab.profile': 'Profile',
|
|
'common.error': 'Error',
|
|
'common.loading': 'Loading…',
|
|
'common.save': 'Save',
|
|
'common.delete': 'Delete',
|
|
'common.cancel': 'Cancel',
|
|
'common.search': 'Search',
|
|
'common.settings': 'Settings',
|
|
'common.logout': 'Log out',
|
|
'note.title': 'Title…',
|
|
'note.contentPlaceholder': 'Content…',
|
|
'note.deleteTitle': 'Delete note',
|
|
'note.deleteConfirm': 'Are you sure you want to delete this note?',
|
|
'note.saveError': 'Save error',
|
|
'note.deleteError': 'Could not delete the note',
|
|
'note.dayNote': 'Note of the day',
|
|
'note.dayNoteError': 'Could not load the note of the day.',
|
|
'note.editTitleOnly': 'Rich content cannot be edited from mobile. Use the web version to modify the text.',
|
|
'note.saveTitle': 'Save title',
|
|
'note.saving': 'Saving…',
|
|
'auth.loginFailed': 'Login failed',
|
|
'auth.googleFailed': 'Google sign-in failed',
|
|
'auth.serverError': 'Server error',
|
|
'auth.unauthorized': 'Unauthorized',
|
|
'auth.emailPlaceholder': 'Email',
|
|
'auth.passwordPlaceholder': 'Password',
|
|
'auth.loginButton': 'Sign in',
|
|
'auth.orGoogle': 'or',
|
|
'revision.title': 'Review',
|
|
'revision.empty': 'No cards to review',
|
|
'revision.loadingError': 'Loading error',
|
|
'revision.difficult': 'Hard',
|
|
'revision.hard': 'Difficult',
|
|
'revision.good': 'Good',
|
|
'revision.easy': 'Easy',
|
|
'profile.title': 'Profile',
|
|
'search.placeholder': 'Search notes…',
|
|
'search.noResults': 'No results',
|
|
'ai.improve': '✨ Improve with AI',
|
|
},
|
|
fa: {
|
|
'tab.home': 'خانه',
|
|
'tab.notebooks': 'دفترچهها',
|
|
'tab.revision': 'مرور',
|
|
'tab.search': 'جستجو',
|
|
'tab.profile': 'پروفایل',
|
|
'common.error': 'خطا',
|
|
'common.loading': 'در حال بارگذاری…',
|
|
'common.save': 'ذخیره',
|
|
'common.delete': 'حذف',
|
|
'common.cancel': 'انصراف',
|
|
'common.search': 'جستجو',
|
|
'common.settings': 'تنظیمات',
|
|
'common.logout': 'خروج',
|
|
'note.title': 'عنوان…',
|
|
'note.contentPlaceholder': 'محتوا…',
|
|
'note.deleteTitle': 'حذف یادداشت',
|
|
'note.deleteConfirm': 'آیا از حذف این یادداشت مطمئن هستید؟',
|
|
'note.saveError': 'خطای ذخیرهسازی',
|
|
'note.deleteError': 'حذف یادداشت امکانپذیر نیست',
|
|
'note.dayNote': 'یادداشت روز',
|
|
'note.dayNoteError': 'بارگذاری یادداشت روز امکانپذیر نیست.',
|
|
'note.editTitleOnly': 'محتوای غنی از موبایل قابل ویرایش نیست. برای ویرایش متن از نسخه وب استفاده کنید.',
|
|
'note.saveTitle': 'ذخیره عنوان',
|
|
'note.saving': 'در حال ذخیره…',
|
|
'auth.loginFailed': 'ورود ناموفق',
|
|
'auth.googleFailed': 'ورود با گوگل ناموفق',
|
|
'auth.serverError': 'خطای سرور',
|
|
'auth.unauthorized': 'غیر مجاز',
|
|
'auth.emailPlaceholder': 'ایمیل',
|
|
'auth.passwordPlaceholder': 'رمز عبور',
|
|
'auth.loginButton': 'ورود',
|
|
'auth.orGoogle': 'یا',
|
|
'revision.title': 'مرور',
|
|
'revision.empty': 'کارت مروری موجود نیست',
|
|
'revision.loadingError': 'خطای بارگذاری',
|
|
'revision.difficult': 'سخت',
|
|
'revision.hard': 'دشوار',
|
|
'revision.good': 'خوب',
|
|
'revision.easy': 'آسان',
|
|
'profile.title': 'پروفایل',
|
|
'search.placeholder': 'جستجوی یادداشت…',
|
|
'search.noResults': 'نتیجهای یافت نشد',
|
|
'ai.improve': '✨ بهبود با هوش مصنوعی',
|
|
},
|
|
}
|
|
|
|
const RTL_LANGS: MobileLang[] = ['fa', 'ar']
|
|
|
|
function detectLang(): MobileLang {
|
|
try {
|
|
const locales = getLocales()
|
|
const lang = locales[0]?.languageCode ?? 'fr'
|
|
if (lang === 'fa') return 'fa'
|
|
if (lang === 'en') return 'en'
|
|
return 'fr'
|
|
} catch {
|
|
return 'fr'
|
|
}
|
|
}
|
|
|
|
let currentLang: MobileLang = detectLang()
|
|
|
|
export function setLang(lang: MobileLang) {
|
|
currentLang = lang
|
|
const isRtl = RTL_LANGS.includes(lang)
|
|
I18nManager.forceRTL(isRtl)
|
|
}
|
|
|
|
export function isRTL(): boolean {
|
|
return RTL_LANGS.includes(currentLang)
|
|
}
|
|
|
|
export function getLang(): MobileLang {
|
|
return currentLang
|
|
}
|
|
|
|
export function t(key: string): string {
|
|
return translations[currentLang]?.[key] ?? translations.fr[key] ?? translations.en[key] ?? key
|
|
}
|