Files
Momento/memento-mobile/lib/i18n.ts
Antigravity a9e43d7594
All checks were successful
CI / Lint, Unit Tests & Build (push) Successful in 6m16s
CI / Deploy production (on server) (push) Successful in 22s
fix(audit): mobile i18n + offline share + silent catches + mobile fixes
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
2026-07-05 16:41:12 +00:00

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
}