feat(translations): add French language support and improve localization handling
This commit is contained in:
@@ -3,7 +3,6 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { Menu, User, LogOut, Settings, Bell, Lock, CheckCircle, Trash2, Sun, Moon } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useLocalizedDate } from '../../hooks/useLocalizedDate';
|
||||
import { useLocalizedTimeAgo } from '../../hooks/useLocalizedTimeAgo';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import { useAdminAuth } from '../../contexts';
|
||||
@@ -25,8 +24,7 @@ export const AdminHeader: React.FC<AdminHeaderProps> = ({ onMenuClick }) => {
|
||||
const { user, logout } = useAdminAuth();
|
||||
const { isDark, toggle: toggleDarkMode, forcedMode } = useAdminDarkMode();
|
||||
const { t } = useTranslation();
|
||||
const { format } = useLocalizedDate();
|
||||
const { formatTimeAgo } = useLocalizedTimeAgo();
|
||||
const { format, formatDistanceToNow } = useLocalizedDate();
|
||||
const [showUserMenu, setShowUserMenu] = useState(false);
|
||||
const [showNotifications, setShowNotifications] = useState(false);
|
||||
const [showPasswordModal, setShowPasswordModal] = useState(false);
|
||||
@@ -190,7 +188,7 @@ export const AdminHeader: React.FC<AdminHeaderProps> = ({ onMenuClick }) => {
|
||||
{notificationsService.formatNotificationMessage(notification)}
|
||||
</p>
|
||||
<p className="text-xs text-neutral-500 dark:text-neutral-400 mt-1">
|
||||
{formatTimeAgo(notification.createdAt)}
|
||||
{formatDistanceToNow(notification.createdAt, { addSuffix: true })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -106,7 +106,7 @@ export const BackupDashboard = ({ status, config, onRunBackup, isBackupRunning }
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold text-neutral-900 dark:text-neutral-100">{t('backup.dashboard.health.title')}</h3>
|
||||
<span className={`px-3 py-1 rounded-full text-sm font-medium bg-${healthColors[health.status]}-100 dark:bg-${healthColors[health.status]}-900/40 text-${healthColors[health.status]}-700 dark:text-${healthColors[health.status]}-300`}>
|
||||
{health.status.charAt(0).toUpperCase() + health.status.slice(1)}
|
||||
{t(`backup.dashboard.healthStatus.${health.status}`)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -46,19 +46,28 @@ const NLFlag: React.FC<{ className?: string }> = ({ className = "w-5 h-5" }) =>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const languages = [
|
||||
const FRFlag: React.FC<{ className?: string }> = ({ className = "w-5 h-5" }) => (
|
||||
<svg className={className} viewBox="0 0 640 480" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="#002395" d="M0 0h213.3v480H0z"/>
|
||||
<path fill="#fff" d="M213.3 0h213.4v480H213.3z"/>
|
||||
<path fill="#ED2939" d="M426.7 0H640v480H426.7z"/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const SUPPORTED_LANGUAGES = [
|
||||
{ code: 'en', name: 'English', Flag: GBFlag },
|
||||
{ code: 'de', name: 'Deutsch', Flag: DEFlag },
|
||||
{ code: 'ru', name: 'Русский', Flag: RUFlag },
|
||||
{ code: 'pt', name: 'Português', Flag: PTBRFlag },
|
||||
{ code: 'nl', name: 'Nederlands', Flag: NLFlag },
|
||||
{ code: 'fr', name: 'Français', Flag: FRFlag },
|
||||
];
|
||||
|
||||
export const LanguageSelector: React.FC = () => {
|
||||
const { i18n } = useTranslation();
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
|
||||
const currentLanguage = languages.find(lang => lang.code === i18n.language) || languages[0];
|
||||
const currentLanguage = SUPPORTED_LANGUAGES.find(lang => lang.code === i18n.language) || SUPPORTED_LANGUAGES[0];
|
||||
|
||||
const handleLanguageChange = (languageCode: string) => {
|
||||
i18n.changeLanguage(languageCode);
|
||||
@@ -78,7 +87,7 @@ export const LanguageSelector: React.FC = () => {
|
||||
|
||||
{isOpen && (
|
||||
<div className="absolute right-0 mt-2 w-48 bg-white dark:bg-neutral-800 rounded-lg shadow-lg border border-neutral-200 dark:border-neutral-700 py-1 z-50">
|
||||
{languages.map((language) => (
|
||||
{SUPPORTED_LANGUAGES.map((language) => (
|
||||
<button
|
||||
key={language.code}
|
||||
onClick={() => handleLanguageChange(language.code)}
|
||||
|
||||
@@ -81,12 +81,13 @@ export const PhotoFilterBar: React.FC<PhotoFilterBarProps> = ({
|
||||
onClick={() => setShowSortMenu(!showSortMenu)}
|
||||
className="w-full md:w-auto text-sm md:text-base"
|
||||
>
|
||||
<span className="hidden md:inline">{t('common.sortBy')} </span>
|
||||
{sortBy === 'date' ? t('gallery.sortByDate').replace('Sort by ', '') :
|
||||
sortBy === 'name' ? t('gallery.sortByName').replace('Sort by ', '') :
|
||||
sortBy === 'size' ? t('gallery.sortBySize').replace('Sort by ', '') :
|
||||
<span className="hidden md:inline">{t('common.sortBy')}{" "}
|
||||
{sortBy === 'date' ? t('gallery.sortByDate').replace(t('common.sortBy'), '') :
|
||||
sortBy === 'name' ? t('gallery.sortByName').replace(t('common.sortBy'), '') :
|
||||
sortBy === 'size' ? t('gallery.sortBySize').replace(t('common.sortBy'), '') :
|
||||
sortBy === 'capture_date' ? t('photoSort.dateTaken', 'Date Taken') :
|
||||
t('gallery.sortByRating', 'Rating')}
|
||||
</span>
|
||||
</Button>
|
||||
|
||||
{showSortMenu && (
|
||||
|
||||
Reference in New Issue
Block a user