feat(i18n): Hebrew locale + full RTL support
This commit is contained in:
@@ -123,7 +123,7 @@ export function ContextMenuItem({
|
||||
role="menuitem"
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
"w-full px-3 py-1.5 text-sm text-left flex items-center gap-2",
|
||||
"w-full px-3 py-1.5 text-sm text-start flex items-center gap-2",
|
||||
"transition-colors duration-150",
|
||||
"focus:outline-none focus:bg-muted",
|
||||
disabled && "opacity-50 cursor-not-allowed",
|
||||
@@ -139,7 +139,7 @@ export function ContextMenuItem({
|
||||
{Icon && <Icon className="w-4 h-4 flex-shrink-0" />}
|
||||
<span className="flex-1">{label}</span>
|
||||
{shortcut && (
|
||||
<span className="text-xs text-muted-foreground ml-auto">{shortcut}</span>
|
||||
<span className="text-xs text-muted-foreground ms-auto">{shortcut}</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -245,6 +245,21 @@ export function FlagIR(props: FlagProps) {
|
||||
);
|
||||
}
|
||||
|
||||
/** Israel – white field, two blue stripes, Star of David */
|
||||
export function FlagIL(props: FlagProps) {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 160" width={W} height={H} className={flagClass} {...props}>
|
||||
<rect width="220" height="160" fill="#fff" />
|
||||
<rect width="220" height="20" y="25" fill="#0038b8" />
|
||||
<rect width="220" height="20" y="115" fill="#0038b8" />
|
||||
<g fill="none" stroke="#0038b8" strokeWidth="5.5">
|
||||
<path d="M110 58 L91 91 L129 91 Z" />
|
||||
<path d="M110 102 L91 69 L129 69 Z" />
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
const skShield =
|
||||
"m269.993 459.98-3.906-1.867c-25.267-12.173-56.294-30.4-81.294-58.133" +
|
||||
"-25-27.733-43.8-65.307-43.8-114.24 0-93.6 4.52-136.68 4.52-136.68" +
|
||||
@@ -280,7 +295,7 @@ export function FlagSK(props: FlagProps) {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/** Map locale codes to flag components */
|
||||
export const flagComponents: Record<string, (props: FlagProps) => ReactElement> = {
|
||||
cs: FlagCS,
|
||||
sk: FlagSK,
|
||||
@@ -303,4 +318,5 @@ export const flagComponents: Record<string, (props: FlagProps) => ReactElement>
|
||||
uk: FlagUA,
|
||||
zh: FlagCN,
|
||||
fa: FlagIR,
|
||||
he: FlagIL,
|
||||
};
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { useLocale } from 'next-intl';
|
||||
import { useLocaleStore } from '@/stores/locale-store';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { flagComponents } from './flag-icons';
|
||||
|
||||
const languages = [
|
||||
{ value: 'auto', label: 'Auto' },
|
||||
{ value: 'cs', label: 'Česky' },
|
||||
{ value: 'sk', label: 'Slovenčina' },
|
||||
{ value: 'da', label: 'Dansk' },
|
||||
@@ -16,6 +16,7 @@ const languages = [
|
||||
{ value: 'fa', label: 'فارسی' },
|
||||
{ value: 'es', label: 'Español' },
|
||||
{ value: 'fr', label: 'Français' },
|
||||
{ value: 'he', label: 'עברית' },
|
||||
{ value: 'it', label: 'Italiano' },
|
||||
{ value: 'hu', label: 'Magyar' },
|
||||
{ value: 'lv', label: 'Latviešu' },
|
||||
@@ -38,13 +39,13 @@ function FlagIcon({ locale }: { locale: string }) {
|
||||
}
|
||||
|
||||
export function LanguageSwitcher({ className }: { className?: string }) {
|
||||
const currentLocale = useLocale();
|
||||
const setLocale = useLocaleStore((state) => state.setLocale);
|
||||
const choice = useLocaleStore((state) => state.locale) || 'auto';
|
||||
const [open, setOpen] = useState(false);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const listRef = useRef<HTMLUListElement>(null);
|
||||
|
||||
const current = languages.find((l) => l.value === currentLocale) ?? languages[0];
|
||||
const current = languages.find((l) => l.value === choice) ?? languages[0];
|
||||
|
||||
// Close on outside click
|
||||
useEffect(() => {
|
||||
@@ -78,7 +79,7 @@ export function LanguageSwitcher({ className }: { className?: string }) {
|
||||
aria-expanded={open}
|
||||
>
|
||||
<FlagIcon locale={current.value} />
|
||||
<span className="flex-1 text-left">{current.label}</span>
|
||||
<span className="flex-1 text-start">{current.label}</span>
|
||||
<ChevronDown className={cn("h-3.5 w-3.5 text-muted-foreground transition-transform duration-150", open && "rotate-180")} />
|
||||
</button>
|
||||
|
||||
@@ -86,22 +87,22 @@ export function LanguageSwitcher({ className }: { className?: string }) {
|
||||
<ul
|
||||
ref={listRef}
|
||||
role="listbox"
|
||||
aria-activedescendant={`lang-${currentLocale}`}
|
||||
className="absolute z-50 mt-1 w-full max-h-60 overflow-auto rounded-md border border-border bg-background shadow-lg py-1"
|
||||
aria-activedescendant={`lang-${choice}`}
|
||||
className="absolute start-0 z-50 mt-1 min-w-full w-max max-w-[min(16rem,80vw)] max-h-60 overflow-auto rounded-md border border-border bg-background shadow-lg py-1"
|
||||
>
|
||||
{languages.map((lang) => (
|
||||
<li
|
||||
key={lang.value}
|
||||
id={`lang-${lang.value}`}
|
||||
role="option"
|
||||
aria-selected={lang.value === currentLocale}
|
||||
aria-selected={lang.value === choice}
|
||||
onClick={() => {
|
||||
setLocale(lang.value);
|
||||
setOpen(false);
|
||||
}}
|
||||
className={cn(
|
||||
"flex items-center gap-2 px-3 py-1.5 text-sm cursor-pointer transition-colors duration-100",
|
||||
lang.value === currentLocale
|
||||
"flex items-center gap-2 px-3 py-1.5 text-sm cursor-pointer whitespace-nowrap transition-colors duration-100",
|
||||
lang.value === choice
|
||||
? "bg-accent text-accent-foreground font-medium"
|
||||
: "text-foreground hover:bg-accent/50"
|
||||
)}
|
||||
|
||||
@@ -101,7 +101,7 @@ export function ToastItem({ toast, onClose }: ToastProps) {
|
||||
{/* Left accent bar */}
|
||||
<div className={cn("absolute left-0 top-0 bottom-0 w-1", progressBarStyles[toast.type])} />
|
||||
|
||||
<div className="flex items-start gap-3 p-3.5 pl-4.5 flex-1 min-w-0">
|
||||
<div className="flex items-start gap-3 p-3.5 ps-4.5 flex-1 min-w-0">
|
||||
{/* Icon */}
|
||||
{toast.icon !== undefined ? (
|
||||
toast.icon
|
||||
|
||||
@@ -111,7 +111,7 @@ export function WelcomeBanner() {
|
||||
onClick={() => { dismiss(); startTour(); }}
|
||||
className="text-xs h-7"
|
||||
>
|
||||
<PlayCircle className="w-3.5 h-3.5 mr-1" />
|
||||
<PlayCircle className="w-3.5 h-3.5 me-1" />
|
||||
{t("start_tour")}
|
||||
</Button>
|
||||
<Button
|
||||
@@ -120,7 +120,7 @@ export function WelcomeBanner() {
|
||||
onClick={() => { dismiss(); router.push('/settings'); }}
|
||||
className="text-xs h-7"
|
||||
>
|
||||
<Settings className="w-3.5 h-3.5 mr-1" />
|
||||
<Settings className="w-3.5 h-3.5 me-1" />
|
||||
{t("settings")}
|
||||
</Button>
|
||||
<Button
|
||||
|
||||
Reference in New Issue
Block a user