jalil.arfaoui.net/src/utils/i18n.ts
Jalil Arfaoui ae565d46ac Migration Astro v4 → v5 avec Content Layer API
- Mise à jour astro@5.17, @astrojs/tailwind@6, @astrojs/check
- Remplacement des content collections legacy par des loaders glob()
- Déplacement src/content/config.ts → src/content.config.ts
- entry.slug → entry.id, entry.render() → render(entry)
- Ajout de generateId personnalisé pour préserver les points dans les IDs des fichiers multilingues (.en, .ar)
2026-02-18 18:11:29 +01:00

176 lines
No EOL
7.4 KiB
TypeScript

export const defaultLocale = 'fr';
export const locales = ['fr', 'en', 'ar'] as const;
export type Locale = typeof locales[number];
export function getLocaleFromUrl(url: URL): Locale {
const [, locale] = url.pathname.split('/');
if (locales.includes(locale as Locale)) {
return locale as Locale;
}
return defaultLocale;
}
export function getLocalizedPath(path: string, locale: Locale): string {
if (locale === defaultLocale) {
return path;
}
return `/${locale}${path}`;
}
export function removeLocaleFromPath(path: string): string {
const segments = path.split('/').filter(Boolean);
if (locales.includes(segments[0] as Locale)) {
segments.shift();
}
return '/' + segments.join('/');
}
export interface LocalizedContent {
fr: string;
en: string;
ar: string;
}
export const translations = {
nav: {
home: { fr: 'Accueil', en: 'Home', ar: 'الرئيسية' },
pro: { fr: 'Pro', en: 'Pro', ar: 'مهني' },
comedy: { fr: 'Comédie', en: 'Comedy', ar: 'كوميديا' },
photo: { fr: 'Photo', en: 'Photo', ar: 'تصوير' },
blog: { fr: 'Blog', en: 'Blog', ar: 'مدونة' },
about: { fr: 'À propos', en: 'About', ar: 'حول' },
projects: { fr: 'Projets', en: 'Projects', ar: 'مشاريع' },
talks: { fr: 'Talks', en: 'Talks', ar: 'محاضرات' },
gallery: { fr: 'Galerie', en: 'Gallery', ar: 'معرض' },
shows: { fr: 'Spectacles', en: 'Shows', ar: 'عروض' },
now: { fr: 'Maintenant', en: 'Now', ar: 'الآن' },
uses: { fr: 'Utilise', en: 'Uses', ar: 'يستخدم' },
},
common: {
siteName: { fr: 'Jalil Arfaoui', en: 'Jalil Arfaoui', ar: 'جليل عرفاوي' },
readMore: { fr: 'Lire la suite', en: 'Read more', ar: 'اقرأ المزيد' },
backToHome: { fr: 'Retour à l\'accueil', en: 'Back to home', ar: 'العودة إلى الرئيسية' },
publishedOn: { fr: 'Publié le', en: 'Published on', ar: 'نشر في' },
by: { fr: 'par', en: 'by', ar: 'بواسطة' },
allPosts: { fr: 'Tous les articles', en: 'All posts', ar: 'جميع المقالات' },
recentPosts: { fr: 'Articles récents', en: 'Recent posts', ar: 'المقالات الأخيرة' },
categories: { fr: 'Catégories', en: 'Categories', ar: 'الفئات' },
tags: { fr: 'Tags', en: 'Tags', ar: 'علامات' },
search: { fr: 'Rechercher', en: 'Search', ar: 'بحث' },
darkMode: { fr: 'Mode sombre', en: 'Dark mode', ar: 'الوضع الداكن' },
lightMode: { fr: 'Mode clair', en: 'Light mode', ar: 'الوضع الفاتح' },
},
categories: {
pro: { fr: 'Professionnel', en: 'Professional', ar: 'مهني' },
comedy: { fr: 'Comédie', en: 'Comedy', ar: 'كوميديا' },
photo: { fr: 'Photographie', en: 'Photography', ar: 'تصوير' },
dev: { fr: 'Développement', en: 'Development', ar: 'تطوير' },
},
photo: {
galleryTitle: { fr: 'Galerie Photo', en: 'Photo Gallery', ar: 'معرض الصور' },
blogTitle: { fr: 'Blog Photo', en: 'Photo Blog', ar: 'مدونة الصور' },
photoFeed: { fr: 'Fil Photo', en: 'Photo Feed', ar: 'سلسلة الصور' },
categories: { fr: 'Catégories', en: 'Categories', ar: 'الفئات' },
browseByTheme: { fr: 'Parcourir les photos par thème', en: 'Browse photos by theme', ar: 'تصفّح الصور حسب الموضوع' },
feedDescription: { fr: 'Parcourir les séries chronologiques, reportages et histoires en images', en: 'Browse chronological series, reports and stories in images', ar: 'تصفّح السلاسل الزمنية والتقارير والقصص المصوّرة' },
viewFeed: { fr: 'Voir le fil', en: 'View feed', ar: 'عرض السلسلة' },
featured: { fr: 'À la une', en: 'Featured', ar: 'مميّز' },
about: { fr: 'À propos', en: 'About', ar: 'نبذة' },
contact: { fr: 'Contact', en: 'Contact', ar: 'تواصل' },
fullscreen: { fr: 'Plein écran', en: 'Fullscreen', ar: 'شاشة كاملة' },
close: { fr: 'Fermer', en: 'Close', ar: 'إغلاق' },
previousImage: { fr: 'Image précédente', en: 'Previous image', ar: 'الصورة السابقة' },
nextImage: { fr: 'Image suivante', en: 'Next image', ar: 'الصورة التالية' },
goToImage: { fr: "Aller à l'image", en: 'Go to image', ar: 'انتقل إلى الصورة' },
},
pages: {
home: {
title: { fr: 'Jalil Arfaoui', en: 'Jalil Arfaoui', ar: 'جليل عرفاوي' },
subtitle: {
fr: 'Développeur • Comédien • Photographe',
en: 'Developer • Comedian • Photographer',
ar: 'مطور • ممثل كوميدي • مصور'
},
description: {
fr: 'Bienvenue dans mon univers créatif où le code rencontre l\'art',
en: 'Welcome to my creative universe where code meets art',
ar: 'مرحبًا بكم في عالمي الإبداعي حيث يلتقي الكود بالفن'
}
},
pro: {
title: { fr: 'Parcours Professionnel', en: 'Professional Journey', ar: 'المسار المهني' },
description: {
fr: 'Développeur passionné par le Software Craftsmanship',
en: 'Developer passionate about Software Craftsmanship',
ar: 'مطور شغوف بحرفية البرمجيات'
}
},
comedy: {
title: { fr: 'Univers Comédie', en: 'Comedy Universe', ar: 'عالم الكوميديا' },
description: {
fr: 'Acteur et créateur de contenus humoristiques',
en: 'Actor and creator of humorous content',
ar: 'ممثل ومنشئ محتوى فكاهي'
}
},
photo: {
title: { fr: 'Portfolio Photo', en: 'Photo Portfolio', ar: 'معرض الصور' },
description: {
fr: 'Capturer l\'instant, raconter une histoire',
en: 'Capturing the moment, telling a story',
ar: 'التقاط اللحظة، سرد قصة'
}
}
}
};
/** Helper de traduction typé */
export function t(section: keyof typeof translations, key: string, locale: Locale): string {
const sectionData = translations[section] as Record<string, LocalizedContent>;
return sectionData?.[key]?.[locale] ?? sectionData?.[key]?.fr ?? key;
}
/** Chemin de base photo selon la langue */
export function getPhotoBasePath(locale: Locale): string {
if (locale === 'ar') return '/ar/تصوير';
if (locale === 'en') return '/en/photo';
return '/photo';
}
/** Chemin du blog photo selon la langue */
export function getPhotoBlogPath(locale: Locale): string {
return `${getPhotoBasePath(locale)}/${locale === 'ar' ? 'مدونة' : 'blog'}`;
}
/** Chemin des albums photo selon la langue */
export function getPhotoAlbumsPath(locale: Locale): string {
return `${getPhotoBasePath(locale)}/${locale === 'ar' ? 'ألبومات' : 'albums'}`;
}
/** Chemin "À propos" selon la langue */
export function getAboutPath(locale: Locale): string {
if (locale === 'en') return '/en/about';
if (locale === 'ar') return '/ar/نبذة-عني';
return '/a-propos';
}
/** Locale pour les dates */
export function getDateLocale(locale: Locale): string {
const dateLocales: Record<Locale, string> = { fr: 'fr-FR', en: 'en-US', ar: 'ar-SA' };
return dateLocales[locale];
}
/** ID de catégorie localisé (portraits → portraits.en pour EN) */
export function getCategoryEntryId(categoryId: string, locale: Locale): string {
return locale === 'fr' ? categoryId : `${categoryId}.${locale}`;
}
/** Chemin d'accueil selon la langue */
export function getHomePath(locale: Locale): string {
return locale === 'fr' ? '/' : `/${locale}`;
}
/** Slug de base d'un photo blog post depuis son id (ex: "2015/enigma.en" → "enigma") */
export function getPostBaseSlug(postId: string): string {
return postId.replace(/^\d{4}\//, '').replace(/\.(en|ar)(\.md)?$/, '');
}