feat(i18n): Complete internationalization with timezone fixes and language switching
Completes full internationalization coverage for the webmail application with enhanced language support: - Replace all remaining hardcoded English strings with translation keys - Add timezone auto-detection to prevent hydration mismatches - Enable instant client-side language switching without page reload - Add 60+ new translation keys in English and French locales - Improve language switcher component with proper state management - Add health check endpoint for container orchestration All user-facing text now supports English and French with no hardcoded fallbacks, providing a fully localized experience.
This commit is contained in:
@@ -38,7 +38,7 @@ export function MobileHeader({
|
||||
<header
|
||||
className={cn(
|
||||
"flex items-center justify-between px-4 h-14 border-b border-border bg-background shrink-0",
|
||||
"md:hidden", // Only visible on mobile
|
||||
"lg:hidden", // Only visible on mobile/tablet
|
||||
className
|
||||
)}
|
||||
>
|
||||
@@ -115,7 +115,7 @@ export function MobileViewerHeader({
|
||||
<header
|
||||
className={cn(
|
||||
"flex items-center justify-between px-2 h-14 border-b border-border bg-background shrink-0",
|
||||
"md:hidden",
|
||||
"lg:hidden", // Only visible on mobile/tablet
|
||||
className
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { useRouter } from "@/i18n/navigation";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
@@ -37,6 +37,7 @@ interface SidebarProps {
|
||||
onMailboxSelect?: (mailboxId: string) => void;
|
||||
onCompose?: () => void;
|
||||
onLogout?: () => void;
|
||||
onSidebarClose?: () => void;
|
||||
onSearch?: (query: string) => void;
|
||||
onClearSearch?: () => void;
|
||||
activeSearchQuery?: string;
|
||||
@@ -94,6 +95,7 @@ function MailboxTreeItem({
|
||||
onToggleExpand: (id: string) => void;
|
||||
isCollapsed: boolean;
|
||||
}) {
|
||||
const t = useTranslations('sidebar');
|
||||
const hasChildren = node.children.length > 0;
|
||||
const isExpanded = expandedFolders.has(node.id);
|
||||
const Icon = getIconForMailbox(node.role, node.name, hasChildren, isExpanded, node.isShared, node.id);
|
||||
@@ -111,12 +113,11 @@ function MailboxTreeItem({
|
||||
<div
|
||||
{...(globalDragging ? dropHandlers : {})}
|
||||
className={cn(
|
||||
"group w-full flex items-center px-2 py-1 text-sm transition-all duration-200",
|
||||
"group w-full flex items-center px-2 py-1 lg:py-1 max-lg:py-3 max-lg:min-h-[44px] text-sm transition-all duration-200",
|
||||
selectedMailbox === node.id
|
||||
? "bg-accent text-accent-foreground"
|
||||
: "hover:bg-muted text-foreground",
|
||||
node.depth === 0 && "font-medium", // Root folders are slightly bolder
|
||||
// Drop target visual feedback
|
||||
node.depth === 0 && "font-medium",
|
||||
isValidDropTarget && "bg-primary/20 ring-2 ring-primary ring-inset",
|
||||
isInvalidDropTarget && "bg-destructive/10 ring-2 ring-destructive/30 ring-inset opacity-50"
|
||||
)}
|
||||
@@ -133,7 +134,7 @@ function MailboxTreeItem({
|
||||
"hover:bg-muted active:bg-accent"
|
||||
)}
|
||||
style={{ marginLeft: indentPixels }}
|
||||
title={isExpanded ? "Collapse" : "Expand"}
|
||||
title={isExpanded ? t('collapse_tooltip') : t('expand_tooltip')}
|
||||
>
|
||||
{isExpanded ? (
|
||||
<ChevronDown className="w-3 h-3 text-muted-foreground" />
|
||||
@@ -148,7 +149,7 @@ function MailboxTreeItem({
|
||||
onClick={() => !isVirtualNode && onMailboxSelect?.(node.id)}
|
||||
disabled={isVirtualNode}
|
||||
className={cn(
|
||||
"flex-1 flex items-center text-left py-1 px-1 rounded",
|
||||
"flex-1 flex items-center text-left py-1 lg:py-1 max-lg:py-2 px-1 rounded",
|
||||
"transition-colors duration-150",
|
||||
isVirtualNode && "cursor-default"
|
||||
)}
|
||||
@@ -208,6 +209,7 @@ export function Sidebar({
|
||||
onMailboxSelect,
|
||||
onCompose,
|
||||
onLogout,
|
||||
onSidebarClose,
|
||||
onSearch,
|
||||
onClearSearch,
|
||||
activeSearchQuery = "",
|
||||
@@ -225,7 +227,6 @@ export function Sidebar({
|
||||
useEffect(() => {
|
||||
setSearchQuery(activeSearchQuery);
|
||||
}, [activeSearchQuery]);
|
||||
const params = useParams();
|
||||
const router = useRouter();
|
||||
|
||||
// Load expanded folders from localStorage on mount
|
||||
@@ -313,21 +314,36 @@ export function Sidebar({
|
||||
className={cn(
|
||||
"relative flex flex-col h-full border-r transition-all duration-300 overflow-hidden",
|
||||
"bg-secondary border-border",
|
||||
isCollapsed ? "w-16" : "w-64",
|
||||
"max-lg:w-full",
|
||||
isCollapsed ? "lg:w-16" : "lg:w-64",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between px-4 py-3 border-b border-border">
|
||||
<div className="flex items-center gap-2 px-4 py-3 border-b border-border">
|
||||
{/* Mobile/Tablet: Close button */}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={onSidebarClose}
|
||||
className="lg:hidden h-11 w-11 flex-shrink-0"
|
||||
aria-label={t("close")}
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</Button>
|
||||
|
||||
{/* Desktop: Collapse toggle */}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setIsCollapsed(!isCollapsed)}
|
||||
className="hidden lg:flex"
|
||||
>
|
||||
<Menu className="w-5 h-5" />
|
||||
</Button>
|
||||
|
||||
{!isCollapsed && (
|
||||
<Button onClick={onCompose} className="ml-2 flex-1">
|
||||
<Button onClick={onCompose} className="flex-1">
|
||||
<PenSquare className="w-4 h-4 mr-2" />
|
||||
{t("compose")}
|
||||
</Button>
|
||||
@@ -421,7 +437,7 @@ export function Sidebar({
|
||||
<div className="border-t border-border mt-2 pt-2">
|
||||
{/* Settings */}
|
||||
<button
|
||||
onClick={() => router.push(`/${params.locale}/settings`)}
|
||||
onClick={() => router.push('/settings')}
|
||||
className="w-full px-4 py-2 flex items-center justify-between hover:bg-muted transition-colors text-sm"
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
|
||||
Reference in New Issue
Block a user