feat(i18n): Hebrew locale + full RTL support

This commit is contained in:
Shuki Vaknin
2026-07-08 00:07:34 +02:00
committed by Linus Rath
parent 8904d724bb
commit 22418c17cf
113 changed files with 3958 additions and 494 deletions
+65 -65
View File
@@ -333,7 +333,7 @@ function renderClickableRecipients(
return (
<span key={r.email + index} className="inline-flex items-center">
{index > 0 && <span className="text-muted-foreground mr-1">,</span>}
{index > 0 && <span className="text-muted-foreground me-1">,</span>}
<RecipientPopover
name={r.name}
email={r.email}
@@ -382,7 +382,7 @@ export function ContactSidebarPanel({
};
return (
<div className="w-[320px] shrink-0 border-l border-border bg-background flex flex-col h-full animate-in slide-in-from-right-5 duration-200">
<div className="w-[320px] shrink-0 border-s border-border bg-background flex flex-col h-full animate-in slide-in-from-right-5 duration-200">
{/* Header */}
<div className="flex items-center justify-between px-4 py-3 border-b border-border">
<h3 className="text-sm font-semibold text-foreground truncate">{t('contact_sidebar.title')}</h3>
@@ -607,7 +607,7 @@ function SidebarSection({ icon: Icon, title, children }: { icon: React.Component
<Icon className="w-3.5 h-3.5 text-muted-foreground" />
<h4 className="text-xs font-medium text-muted-foreground uppercase tracking-wider">{title}</h4>
</div>
<div className="space-y-1 pl-5.5">{children}</div>
<div className="space-y-1 ps-5.5">{children}</div>
</div>
);
}
@@ -2637,7 +2637,7 @@ export function EmailViewer({
<h3 className="text-xl font-semibold text-foreground mb-3">{tDemoWelcome('title')}</h3>
<p className="text-sm text-muted-foreground mb-6 leading-relaxed">{tDemoWelcome('description')}</p>
<div className="flex flex-col gap-3 items-center">
<div className="grid grid-cols-2 gap-3 text-left text-sm text-muted-foreground w-full">
<div className="grid grid-cols-2 gap-3 text-start text-sm text-muted-foreground w-full">
<div className="flex items-center gap-2">
<Mail className="w-4 h-4 text-primary shrink-0" />
<span>{tDemoWelcome('feature_email')}</span>
@@ -2679,7 +2679,7 @@ export function EmailViewer({
<p className="text-muted-foreground">{t('no_conversation_description')}</p>
{onCompose && (
<Button onClick={onCompose} className="mt-6" title={t('compose_hint')}>
<PenSquare className="w-4 h-4 mr-2" />
<PenSquare className="w-4 h-4 me-2" />
{t('compose')}
</Button>
)}
@@ -2704,7 +2704,7 @@ export function EmailViewer({
variant="ghost"
size="icon"
onClick={onBack}
className="h-9 w-9 flex-shrink-0 -ml-1"
className="h-9 w-9 flex-shrink-0 -ms-1"
aria-label={t('back_to_list')}
>
<ChevronLeft className="w-5 h-5" />
@@ -2839,7 +2839,7 @@ export function EmailViewer({
{showToolbarLabels && <span className="text-[10px] leading-tight sm:text-sm">{t('move')}</span>}
</Button>
{moveMenuOpen && (
<div className="absolute right-0 top-full mt-1 py-1 w-48 max-h-72 overflow-y-auto bg-background rounded-lg shadow-lg border border-border z-10">
<div className="absolute end-0 top-full mt-1 py-1 w-48 max-h-72 overflow-y-auto bg-background rounded-lg shadow-lg border border-border z-10">
{(() => {
const renderNodes = (nodes: MailboxNode[], depth = 0) => {
return nodes.map((node) => {
@@ -2850,7 +2850,7 @@ export function EmailViewer({
{isTarget ? (
<button
onClick={() => { onMoveToMailbox(node.id); setMoveMenuOpen(false); }}
className="w-full px-3 py-1.5 text-sm text-left hover:bg-muted flex items-center gap-2"
className="w-full px-3 py-1.5 text-sm text-start hover:bg-muted flex items-center gap-2"
style={{ paddingLeft: `${0.75 + depth * 1}rem` }}
>
<Icon className="w-4 h-4 flex-shrink-0" />
@@ -2910,7 +2910,7 @@ export function EmailViewer({
)}
</button>
{tagMenuOpen && (
<div className="absolute right-0 top-full mt-1 py-1 w-40 bg-background rounded-lg shadow-lg border border-border z-10">
<div className="absolute end-0 top-full mt-1 py-1 w-40 bg-background rounded-lg shadow-lg border border-border z-10">
{colorOptions.map((option) => {
const isActive = currentColors.includes(option.value);
return (
@@ -2918,13 +2918,13 @@ export function EmailViewer({
key={option.value}
onClick={() => { if (email) onSetColorTag?.(email.id, option.value); setTagMenuOpen(false); }}
className={cn(
"w-full px-3 py-1.5 text-sm text-left hover:bg-muted flex items-center gap-2",
"w-full px-3 py-1.5 text-sm text-start hover:bg-muted flex items-center gap-2",
isActive && "bg-accent font-medium"
)}
>
<span className={cn("w-3 h-3 rounded-full flex-shrink-0", option.color)} />
<span className="truncate">{option.name}</span>
{isActive && <Check className="w-3 h-3 ml-auto flex-shrink-0 text-foreground" />}
{isActive && <Check className="w-3 h-3 ms-auto flex-shrink-0 text-foreground" />}
</button>
);
})}
@@ -2933,7 +2933,7 @@ export function EmailViewer({
<div className="h-px bg-border my-1" />
<button
onClick={() => { if (email) onSetColorTag?.(email.id, null); setTagMenuOpen(false); }}
className="w-full px-3 py-1.5 text-sm text-left hover:bg-muted flex items-center gap-2 text-muted-foreground"
className="w-full px-3 py-1.5 text-sm text-start hover:bg-muted flex items-center gap-2 text-muted-foreground"
>
<X className="w-3 h-3 flex-shrink-0" />
<span>{t('remove_color')}</span>
@@ -3037,11 +3037,11 @@ export function EmailViewer({
<span className="text-[10px] leading-tight sm:hidden">{t('more_actions')}</span>
</Button>
{moreMenuOpen && !isMobile && (
<div className="absolute right-0 top-full mt-1 w-48 bg-background rounded-md shadow-lg border border-border z-10 py-1">
<div className="absolute end-0 top-full mt-1 w-48 bg-background rounded-md shadow-lg border border-border z-10 py-1">
{/* Star toggle */}
<button
onClick={() => { onToggleStar?.(); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className="w-full px-3 py-1.5 text-sm text-left hover:bg-muted text-foreground flex items-center gap-2"
className="w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2"
>
<Star className={cn("w-4 h-4", isStarred && "fill-yellow-400 text-yellow-400")} />
{isStarred ? t('tooltips.unstar') : t('tooltips.star')}
@@ -3049,7 +3049,7 @@ export function EmailViewer({
{/* Overflow: reply */}
<button
onClick={() => { onReply?.(); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className={cn("w-full px-3 py-1.5 text-sm text-left hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(1) ? "" : "sm:hidden")}
className={cn("w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(1) ? "" : "sm:hidden")}
>
<Reply className="w-4 h-4" />
{t('reply')}
@@ -3057,7 +3057,7 @@ export function EmailViewer({
{/* Overflow: reply all */}
<button
onClick={() => { onReplyAll?.(); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className={cn("w-full px-3 py-1.5 text-sm text-left hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(2) ? "" : "sm:hidden")}
className={cn("w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(2) ? "" : "sm:hidden")}
>
<ReplyAll className="w-4 h-4" />
{t('reply_all')}
@@ -3065,7 +3065,7 @@ export function EmailViewer({
{/* Overflow: forward */}
<button
onClick={() => { onForward?.(); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className={cn("w-full px-3 py-1.5 text-sm text-left hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(3) ? "" : "sm:hidden")}
className={cn("w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(3) ? "" : "sm:hidden")}
>
<Forward className="w-4 h-4" />
{t('forward')}
@@ -3073,7 +3073,7 @@ export function EmailViewer({
{/* Overflow: archive */}
<button
onClick={() => { onArchive?.(); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className={cn("w-full px-3 py-1.5 text-sm text-left hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(4) ? "" : "sm:hidden")}
className={cn("w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(4) ? "" : "sm:hidden")}
>
<Archive className="w-4 h-4" />
{t('archive')}
@@ -3086,14 +3086,14 @@ export function EmailViewer({
>
<button
onClick={() => setMoreMenuSub(moreMenuSub === 'move' ? null : 'move')}
className="w-full px-3 py-1.5 text-sm text-left hover:bg-muted text-foreground flex items-center gap-2"
className="w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2"
>
<FolderInput className="w-4 h-4" />
<span className="flex-1">{t('move_to')}</span>
<ChevronRight className="w-3 h-3 text-muted-foreground" />
</button>
{moreMenuSub === 'move' && (
<div className="absolute right-full top-0 mr-1 py-1 w-48 max-h-72 overflow-y-auto bg-background rounded-md shadow-lg border border-border z-10">
<div className="absolute end-full top-0 me-1 py-1 w-48 max-h-72 overflow-y-auto bg-background rounded-md shadow-lg border border-border z-10">
{(() => {
const renderMobileNodes = (nodes: MailboxNode[], depth = 0) => {
return nodes.map((node) => {
@@ -3104,7 +3104,7 @@ export function EmailViewer({
{isTarget ? (
<button
onClick={() => { onMoveToMailbox(node.id); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className="w-full px-3 py-1.5 text-sm text-left hover:bg-muted flex items-center gap-2"
className="w-full px-3 py-1.5 text-sm text-start hover:bg-muted flex items-center gap-2"
style={{ paddingLeft: `${0.75 + depth * 1}rem` }}
>
<Icon className="w-4 h-4 flex-shrink-0" />
@@ -3138,14 +3138,14 @@ export function EmailViewer({
>
<button
onClick={() => setMoreMenuSub(moreMenuSub === 'tag' ? null : 'tag')}
className="w-full px-3 py-1.5 text-sm text-left hover:bg-muted text-foreground flex items-center gap-2"
className="w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2"
>
<Tag className="w-4 h-4" />
<span className="flex-1">{t('tag')}</span>
<ChevronRight className="w-3 h-3 text-muted-foreground" />
</button>
{moreMenuSub === 'tag' && (
<div className="absolute right-full top-0 mr-1 py-1 w-40 bg-background rounded-md shadow-lg border border-border z-10">
<div className="absolute end-full top-0 me-1 py-1 w-40 bg-background rounded-md shadow-lg border border-border z-10">
{colorOptions.map((option) => {
const isActive = currentColors.includes(option.value);
return (
@@ -3153,13 +3153,13 @@ export function EmailViewer({
key={option.value}
onClick={() => { if (email) onSetColorTag?.(email.id, option.value); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className={cn(
"w-full px-3 py-1.5 text-sm text-left hover:bg-muted flex items-center gap-2",
"w-full px-3 py-1.5 text-sm text-start hover:bg-muted flex items-center gap-2",
isActive && "bg-accent font-medium"
)}
>
<span className={cn("w-3 h-3 rounded-full flex-shrink-0", option.color)} />
<span className="truncate">{option.name}</span>
{isActive && <Check className="w-3 h-3 ml-auto flex-shrink-0 text-foreground" />}
{isActive && <Check className="w-3 h-3 ms-auto flex-shrink-0 text-foreground" />}
</button>
);
})}
@@ -3168,7 +3168,7 @@ export function EmailViewer({
<div className="h-px bg-border my-1" />
<button
onClick={() => { if (email) onSetColorTag?.(email.id, null); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className="w-full px-3 py-1.5 text-sm text-left hover:bg-muted flex items-center gap-2 text-muted-foreground"
className="w-full px-3 py-1.5 text-sm text-start hover:bg-muted flex items-center gap-2 text-muted-foreground"
>
<X className="w-3 h-3 flex-shrink-0" />
<span>{t('remove_color')}</span>
@@ -3183,7 +3183,7 @@ export function EmailViewer({
{spamApplicable && (onMarkAsSpam || onUndoSpam) && (
<button
onClick={() => { (isInJunkFolder ? onUndoSpam : onMarkAsSpam)?.(); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className={cn("w-full px-3 py-1.5 text-sm text-left hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(7) ? "" : "sm:hidden")}
className={cn("w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(7) ? "" : "sm:hidden")}
>
{isInJunkFolder ? (
<ShieldCheck className="h-4 w-4 text-green-600 dark:text-green-400" />
@@ -3196,7 +3196,7 @@ export function EmailViewer({
{/* Overflow: toggle read */}
<button
onClick={() => { onMarkAsRead?.(email.id, isUnread); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className={cn("w-full px-3 py-1.5 text-sm text-left hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(8) ? "" : "sm:hidden")}
className={cn("w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(8) ? "" : "sm:hidden")}
>
{isUnread ? <MailOpen className="w-4 h-4" /> : <Mail className="w-4 h-4" />}
{isUnread ? t('mark_read') : t('mark_unread')}
@@ -3204,7 +3204,7 @@ export function EmailViewer({
{/* Overflow: print */}
<button
onClick={() => { handlePrint(); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className={cn("w-full px-3 py-1.5 text-sm text-left hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(9) ? "" : "sm:hidden")}
className={cn("w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(9) ? "" : "sm:hidden")}
>
<Printer className="w-4 h-4" />
{t('print')}
@@ -3212,7 +3212,7 @@ export function EmailViewer({
{/* Overflow: view source */}
<button
onClick={() => { setShowSourceModal(true); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className={cn("w-full px-3 py-1.5 text-sm text-left hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(10) ? "" : "sm:hidden")}
className={cn("w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(10) ? "" : "sm:hidden")}
>
<Code className="w-4 h-4" />
{t('view_source')}
@@ -3221,7 +3221,7 @@ export function EmailViewer({
{effectiveEmailContent.isHtml && (
<button
onClick={() => { setEmailViewDarkOverride(prev => prev === null ? !(resolvedTheme === 'dark') : !prev); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className={cn("w-full px-3 py-1.5 text-sm text-left hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(11) ? "" : "sm:hidden")}
className={cn("w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(11) ? "" : "sm:hidden")}
>
{isDark ? <Sun className="w-4 h-4" /> : <Moon className="w-4 h-4" />}
{isDark ? 'View in light mode' : 'View in dark mode'}
@@ -3231,7 +3231,7 @@ export function EmailViewer({
{/* Export email */}
<button
onClick={() => { handleExportEmail(); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className="w-full px-3 py-1.5 text-sm text-left hover:bg-muted text-foreground flex items-center gap-2"
className="w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2"
>
<Download className="w-4 h-4" />
{t('export_email')}
@@ -3239,7 +3239,7 @@ export function EmailViewer({
{/* Import email */}
<button
onClick={() => { handleImportEmail(); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className="w-full px-3 py-1.5 text-sm text-left hover:bg-muted text-foreground flex items-center gap-2"
className="w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2"
>
<Upload className="w-4 h-4" />
{t('import_email')}
@@ -3247,7 +3247,7 @@ export function EmailViewer({
{onShowShortcuts && (
<button
onClick={() => { onShowShortcuts(); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className="w-full px-3 py-1.5 text-sm text-left hover:bg-muted text-foreground flex items-center gap-2"
className="w-full px-3 py-1.5 text-sm text-start hover:bg-muted text-foreground flex items-center gap-2"
>
<Keyboard className="w-4 h-4" />
{t('keyboard_shortcuts')}
@@ -3275,7 +3275,7 @@ export function EmailViewer({
)}
{!isScheduled && isMobile && (
<div className={cn(
"fixed inset-y-0 right-0 w-72 bg-background border-l border-border z-[70] sm:hidden",
"fixed inset-y-0 right-0 w-72 bg-background border-s border-border z-[70] sm:hidden",
"transform transition-transform duration-300 ease-in-out",
"flex flex-col",
moreMenuOpen ? "translate-x-0" : "translate-x-full"
@@ -3284,7 +3284,7 @@ export function EmailViewer({
{moreMenuSub ? (
<button
onClick={() => setMoreMenuSub(null)}
className="flex items-center gap-1 -ml-2 px-2 py-1 rounded hover:bg-muted text-sm font-semibold text-foreground"
className="flex items-center gap-1 -ms-2 px-2 py-1 rounded hover:bg-muted text-sm font-semibold text-foreground"
>
<ChevronLeft className="w-5 h-5" />
{moreMenuSub === 'move' ? t('move_to') : t('tag')}
@@ -3302,7 +3302,7 @@ export function EmailViewer({
{/* Star toggle */}
<button
onClick={() => { onToggleStar?.(); setMoreMenuOpen(false); }}
className="w-full px-4 py-3 min-h-[44px] text-sm text-left hover:bg-muted text-foreground flex items-center gap-3"
className="w-full px-4 py-3 min-h-[44px] text-sm text-start hover:bg-muted text-foreground flex items-center gap-3"
>
<Star className={cn("w-5 h-5", isStarred && "fill-yellow-400 text-yellow-400")} />
{isStarred ? t('tooltips.unstar') : t('tooltips.star')}
@@ -3311,12 +3311,12 @@ export function EmailViewer({
{colorOptions.length > 0 && (
<button
onClick={() => setMoreMenuSub('tag')}
className="w-full px-4 py-3 min-h-[44px] text-sm text-left hover:bg-muted text-foreground flex items-center gap-3"
className="w-full px-4 py-3 min-h-[44px] text-sm text-start hover:bg-muted text-foreground flex items-center gap-3"
>
<Tag className="w-5 h-5" />
<span className="flex-1">{t('tag')}</span>
{currentColors.length > 0 && (
<div className="flex -space-x-1 mr-1">
<div className="flex -space-x-1 me-1">
{currentColors.slice(0, 3).map((c) => {
const opt = colorOptions.find((o) => o.value === c);
return opt ? <span key={c} className={cn("w-3 h-3 rounded-full border border-background", opt.color)} /> : null;
@@ -3328,14 +3328,14 @@ export function EmailViewer({
)}
<button
onClick={() => { handlePrint(); setMoreMenuOpen(false); }}
className="w-full px-4 py-3 min-h-[44px] text-sm text-left hover:bg-muted text-foreground flex items-center gap-3"
className="w-full px-4 py-3 min-h-[44px] text-sm text-start hover:bg-muted text-foreground flex items-center gap-3"
>
<Printer className="w-5 h-5" />
{t('print')}
</button>
<button
onClick={() => { setShowSourceModal(true); setMoreMenuOpen(false); }}
className="w-full px-4 py-3 min-h-[44px] text-sm text-left hover:bg-muted text-foreground flex items-center gap-3"
className="w-full px-4 py-3 min-h-[44px] text-sm text-start hover:bg-muted text-foreground flex items-center gap-3"
>
<Code className="w-5 h-5" />
{t('view_source')}
@@ -3343,7 +3343,7 @@ export function EmailViewer({
{effectiveEmailContent.isHtml && (
<button
onClick={() => { setEmailViewDarkOverride(prev => prev === null ? !(resolvedTheme === 'dark') : !prev); setMoreMenuOpen(false); }}
className="w-full px-4 py-3 min-h-[44px] text-sm text-left hover:bg-muted text-foreground flex items-center gap-3"
className="w-full px-4 py-3 min-h-[44px] text-sm text-start hover:bg-muted text-foreground flex items-center gap-3"
>
{isDark ? <Sun className="w-5 h-5" /> : <Moon className="w-5 h-5" />}
{isDark ? 'View in light mode' : 'View in dark mode'}
@@ -3352,14 +3352,14 @@ export function EmailViewer({
<div className="h-px bg-border my-1" />
<button
onClick={() => { handleExportEmail(); setMoreMenuOpen(false); }}
className="w-full px-4 py-3 min-h-[44px] text-sm text-left hover:bg-muted text-foreground flex items-center gap-3"
className="w-full px-4 py-3 min-h-[44px] text-sm text-start hover:bg-muted text-foreground flex items-center gap-3"
>
<Download className="w-5 h-5" />
{t('export_email')}
</button>
<button
onClick={() => { handleImportEmail(); setMoreMenuOpen(false); }}
className="w-full px-4 py-3 min-h-[44px] text-sm text-left hover:bg-muted text-foreground flex items-center gap-3"
className="w-full px-4 py-3 min-h-[44px] text-sm text-start hover:bg-muted text-foreground flex items-center gap-3"
>
<Upload className="w-5 h-5" />
{t('import_email')}
@@ -3367,7 +3367,7 @@ export function EmailViewer({
{onShowShortcuts && (
<button
onClick={() => { onShowShortcuts(); setMoreMenuOpen(false); }}
className="w-full px-4 py-3 min-h-[44px] text-sm text-left hover:bg-muted text-foreground flex items-center gap-3"
className="w-full px-4 py-3 min-h-[44px] text-sm text-start hover:bg-muted text-foreground flex items-center gap-3"
>
<Keyboard className="w-5 h-5" />
{t('keyboard_shortcuts')}
@@ -3385,7 +3385,7 @@ export function EmailViewer({
{isTarget ? (
<button
onClick={() => { onMoveToMailbox(node.id); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className="w-full px-4 py-2.5 min-h-[44px] text-sm text-left hover:bg-muted flex items-center gap-3"
className="w-full px-4 py-2.5 min-h-[44px] text-sm text-start hover:bg-muted flex items-center gap-3"
style={{ paddingLeft: `${1 + depth * 1}rem` }}
>
<Icon className="w-5 h-5 flex-shrink-0" />
@@ -3416,20 +3416,20 @@ export function EmailViewer({
key={option.value}
onClick={() => { if (email) onSetColorTag?.(email.id, option.value); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className={cn(
"w-full px-4 py-2.5 min-h-[44px] text-sm text-left hover:bg-muted flex items-center gap-3",
"w-full px-4 py-2.5 min-h-[44px] text-sm text-start hover:bg-muted flex items-center gap-3",
isActive && "bg-accent font-medium"
)}
>
<span className={cn("w-3.5 h-3.5 rounded-full flex-shrink-0", option.color)} />
<span className="truncate">{option.name}</span>
{isActive && <Check className="w-4 h-4 ml-auto flex-shrink-0 text-foreground" />}
{isActive && <Check className="w-4 h-4 ms-auto flex-shrink-0 text-foreground" />}
</button>
);
})}
{currentColors.length > 0 && (
<button
onClick={() => { if (email) onSetColorTag?.(email.id, null); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className="w-full px-4 py-2.5 min-h-[44px] text-sm text-left hover:bg-muted flex items-center gap-3 text-muted-foreground"
className="w-full px-4 py-2.5 min-h-[44px] text-sm text-start hover:bg-muted flex items-center gap-3 text-muted-foreground"
>
<X className="w-4 h-4 flex-shrink-0" />
<span>{t('remove_color')}</span>
@@ -3469,7 +3469,7 @@ export function EmailViewer({
variant="ghost"
size="icon"
onClick={onBack}
className="h-11 w-11 lg:h-10 lg:w-10 flex-shrink-0 -ml-2"
className="h-11 w-11 lg:h-10 lg:w-10 flex-shrink-0 -ms-2"
aria-label={t('back_to_list')}
>
<ChevronLeft className="w-5 h-5" />
@@ -3513,7 +3513,7 @@ export function EmailViewer({
</div>
</div>
{/* Date/time on the right of subject row - hidden on mobile, shown next to sender */}
<div className="hidden sm:block flex-shrink-0 text-right">
<div className="hidden sm:block flex-shrink-0 text-end">
<span className="text-xs lg:text-sm text-muted-foreground whitespace-nowrap">
{formatDateTime(email.receivedAt, timeFormat, { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric' })}
</span>
@@ -3569,7 +3569,7 @@ export function EmailViewer({
name={sender?.name}
email={sender.email}
onViewContact={handleViewContactSidebar}
className="font-semibold text-left"
className="font-semibold text-start"
/>
) : (
<span className="font-semibold text-foreground">{t('unknown_sender')}</span>
@@ -3633,7 +3633,7 @@ export function EmailViewer({
)}
<button
onClick={() => setShowFullHeaders(!showFullHeaders)}
className="text-xs text-muted-foreground hover:text-foreground flex items-center gap-0.5 transition-colors ml-1"
className="text-xs text-muted-foreground hover:text-foreground flex items-center gap-0.5 transition-colors ms-1"
>
{showFullHeaders ? (
<>
@@ -3756,7 +3756,7 @@ export function EmailViewer({
<span className="text-xs text-foreground truncate max-w-[180px]">
{getAttachmentDisplayName(attachment.name, attachment.type)}
</span>
<span className="text-[10px] text-muted-foreground ml-auto flex-shrink-0">
<span className="text-[10px] text-muted-foreground ms-auto flex-shrink-0">
{formatFileSize(attachment.size)}
</span>
<div className="absolute inset-y-0 right-0 rounded-r-md bg-background/95 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center gap-1 px-1.5">
@@ -3814,7 +3814,7 @@ export function EmailViewer({
name={sender?.name}
email={sender.email}
onViewContact={handleViewContactSidebar}
className="text-sm font-semibold text-left"
className="text-sm font-semibold text-start"
/>
) : (
<span className="text-sm font-semibold text-foreground">{t('unknown_sender')}</span>
@@ -3857,7 +3857,7 @@ export function EmailViewer({
)}
<button
onClick={() => setShowFullHeaders(!showFullHeaders)}
className="text-xs text-muted-foreground hover:text-foreground flex items-center gap-0.5 transition-colors ml-1"
className="text-xs text-muted-foreground hover:text-foreground flex items-center gap-0.5 transition-colors ms-1"
>
{showFullHeaders ? (
<>
@@ -3874,7 +3874,7 @@ export function EmailViewer({
</div>
</div>
{/* Date/time + size on the right (mobile) */}
<div className="sm:hidden flex-shrink-0 text-right ml-2">
<div className="sm:hidden flex-shrink-0 text-end ms-2">
<span className="text-xs text-muted-foreground whitespace-nowrap">
{formatDateTime(email.receivedAt, timeFormat, { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric' })}
</span>
@@ -4006,7 +4006,7 @@ export function EmailViewer({
email={sender?.email || ''}
displayLabel={sender?.name && sender?.email ? `${sender.name} <${sender.email}>` : undefined}
onViewContact={handleViewContactSidebar}
className="text-sm text-left"
className="text-sm text-start"
/>
</div>
</Row>
@@ -4534,7 +4534,7 @@ export function EmailViewer({
<span className="text-sm text-foreground truncate max-w-[220px]">
{getAttachmentDisplayName(attachment.name, attachment.type)}
</span>
<span className="text-xs text-muted-foreground ml-auto flex-shrink-0">
<span className="text-xs text-muted-foreground ms-auto flex-shrink-0">
{formatFileSize(attachment.size)}
</span>
<div className="absolute inset-y-0 right-0 rounded-r-md bg-background/95 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center gap-1 px-1.5">
@@ -4672,7 +4672,7 @@ export function EmailViewer({
<span className="text-xs text-foreground truncate max-w-[180px]">
{getAttachmentDisplayName(attachment.name, attachment.type)}
</span>
<span className="text-[10px] text-muted-foreground ml-auto flex-shrink-0">
<span className="text-[10px] text-muted-foreground ms-auto flex-shrink-0">
{formatFileSize(attachment.size)}
</span>
<div className="absolute inset-y-0 right-0 rounded-r-md bg-background/95 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center gap-1 px-1.5">
@@ -4815,7 +4815,7 @@ export function EmailViewer({
disabled={isSendingQuickReply}
className="text-muted-foreground"
>
<MoreVertical className="w-4 h-4 mr-1" />
<MoreVertical className="w-4 h-4 me-1" />
{t('more_options')}
</Button>
<Button
@@ -4825,12 +4825,12 @@ export function EmailViewer({
>
{isSendingQuickReply ? (
<>
<Loader2 className="w-4 h-4 mr-1 animate-spin" />
<Loader2 className="w-4 h-4 me-1 animate-spin" />
{t('sending')}
</>
) : (
<>
<Reply className="w-4 h-4 mr-1" />
<Reply className="w-4 h-4 me-1" />
{t('send')}
</>
)}
@@ -4966,7 +4966,7 @@ export function EmailViewer({
<>
{/* Collapse toggle when sidebar is collapsed */}
{detailSidebarCollapsed && (
<div className="flex flex-col items-center border-l border-border bg-background">
<div className="flex flex-col items-center border-s border-border bg-background">
<Button
variant="ghost"
size="icon"
@@ -4990,7 +4990,7 @@ export function EmailViewer({
onDoubleClick={() => setDetailSidebarWidth(280)}
/>
<div
className="flex flex-col h-full border-l border-border bg-background overflow-hidden"
className="flex flex-col h-full border-s border-border bg-background overflow-hidden"
style={{ width: detailSidebarWidth, minWidth: detailSidebarWidth }}
>
<div className="flex items-center justify-end px-1 py-1 border-b border-border shrink-0">