From d71c111cf30a5a92d4bcdd3fdcfc4e9f518578c0 Mon Sep 17 00:00:00 2001 From: JiriUhlir <149317995+JiriUhlir@users.noreply.github.com> Date: Wed, 26 Aug 2026 10:37:58 +0200 Subject: [PATCH] Jazyky, skutecny snimek v heru a opravene titulky Prepinani jazyku (web/src/i18n/). Vybrany jazyk je jedna hodnota pro celou aplikaci drzena mimo React - tentyz vzorec jako u vybrane firmy, a ze stejneho duvodu: musi byt dostupny i mimo komponenty a prepnuti nesmi byt stav jedne stranky. Volba prezije obnoveni a prepisuje . Anglicky slovnik je Partial: co v nem neni, spadne na cestinu. Kdyby musel byt uplny, znamenal by kazdy novy cesky text rozbity build, dokud ho nekdo neprelozi - a vysledkem by bylo, ze se texty nepridavaji. Prelozena je hlavicka, hero vcetne snimku, pas s logy, cisla, zaverecna vyzva a cast paticky; zbytek je mechanicky a popsany v 23-jazyky.md. Snimek v heru vypada jako portal doopravdy. Byl svetly a ukazoval obrazovku, ktera v produktu neexistuje. Ted ma tmavy podklad, tytez zalozky vcetne prepinace firmy, tytez sloupce tabulky i tytez odznaky stavu jako stranka Tickety. Kdyz se navstevnik prihlasi a uvidi neco jineho, prvni dojem z produktu je, ze web lhal. Jmeno znacky si sklada usePageMeta, ne stranky. Bylo napsane natvrdo ve dvaceti ctyrech titulcich, takze prejmenovani zmenilo brand.ts a titulky ne: hlavicka rikala nove jmeno a zalozka v prohlizeci stare. Opraveno: odkaz "Prohlednout si to" vedl na koren domeny misto /apps//sluzby, protoze to byl a ne Link z routeru. Byl to jediny takovy odkaz v celem webu. Co-Authored-By: Claude Opus 5 (1M context) --- documentation/23-jazyky.md | 84 +++++ documentation/99-zmeny.md | 30 ++ web/src/components/home/CallToAction.tsx | 10 +- web/src/components/home/Hero.tsx | 371 ++++++++++--------- web/src/components/home/LogoCloud.tsx | 4 +- web/src/components/home/Stats.tsx | 17 +- web/src/components/layout/Footer.tsx | 6 +- web/src/components/layout/LanguageSwitch.tsx | 70 ++++ web/src/components/layout/Navbar.tsx | 25 +- web/src/data/navigation.ts | 15 +- web/src/i18n/cs.ts | 106 ++++++ web/src/i18n/en.ts | 104 ++++++ web/src/i18n/index.ts | 119 ++++++ web/src/lib/usePageMeta.ts | 28 +- web/src/pages/About.tsx | 2 +- web/src/pages/Contact.tsx | 2 +- web/src/pages/Home.tsx | 4 +- web/src/pages/Invite.tsx | 2 +- web/src/pages/Login.tsx | 4 +- web/src/pages/NotFound.tsx | 2 +- web/src/pages/Services.tsx | 2 +- web/src/pages/dashboard/ActionDetail.tsx | 2 +- web/src/pages/dashboard/Actions.tsx | 2 +- web/src/pages/dashboard/AutomationDetail.tsx | 2 +- web/src/pages/dashboard/Automations.tsx | 2 +- web/src/pages/dashboard/Connectors.tsx | 2 +- web/src/pages/dashboard/Helpdesk.tsx | 2 +- web/src/pages/dashboard/Incidents.tsx | 2 +- web/src/pages/dashboard/Overview.tsx | 2 +- web/src/pages/dashboard/People.tsx | 2 +- web/src/pages/dashboard/PersonDetail.tsx | 2 +- web/src/pages/dashboard/Scripts.tsx | 2 +- web/src/pages/dashboard/Services.tsx | 2 +- web/src/pages/dashboard/Settings.tsx | 2 +- web/src/pages/dashboard/TicketDetail.tsx | 2 +- web/src/pages/dashboard/Tickets.tsx | 2 +- web/src/pages/dashboard/Widgets.tsx | 2 +- 37 files changed, 807 insertions(+), 232 deletions(-) create mode 100644 documentation/23-jazyky.md create mode 100644 web/src/components/layout/LanguageSwitch.tsx create mode 100644 web/src/i18n/cs.ts create mode 100644 web/src/i18n/en.ts create mode 100644 web/src/i18n/index.ts diff --git a/documentation/23-jazyky.md b/documentation/23-jazyky.md new file mode 100644 index 0000000..57e961e --- /dev/null +++ b/documentation/23-jazyky.md @@ -0,0 +1,84 @@ +# 23 - Jazyky + +Rozdelane. Mechanismus je hotovy a overeny, prevod obsahu bezi po castech. + +## Jak to funguje + +| Co | Kde | +| ------------------- | ---------------------------------------------- | +| Vrstva, prepinani | `web/src/i18n/index.ts` | +| Ceske texty | `web/src/i18n/cs.ts` | +| Anglicke texty | `web/src/i18n/en.ts` | +| Prepinac v rozhrani | `web/src/components/layout/LanguageSwitch.tsx` | + +Vybrany jazyk je **jedna hodnota pro celou aplikaci**, drzena v modulu mimo +React. Je to tentyz vzorec jako u vybrane firmy (`lib/tenant.ts`) a ze stejneho +duvodu: hodnota musi byt dostupna i mimo komponenty a prepnuti nesmi byt stav +jedne stranky. + +Volba se uklada a prezije obnoveni stranky. Pri prvnim nacteni rozhoduje +ulozena volba, pak jazyk prohlizece, pak cestina. + +Pri kazde zmene se prepisuje i ``. Ctecky a prohlizece podle nej +poznaji jazyk stranky a nenabizeji preklad textu, ktery uz prelozeny je. + +## Klice jsou ploche + +```ts +t('hero.titleAccent') // ne t('hero').titleAccent +``` + +Ploche klice jdou vygrepovat: `grep "hero.titleAccent"` najde definici +i vsechna pouziti. U zanorenych objektu to nejde a prave tohle clovek pri +prekladu dela nejcasteji. + +## Chybejici preklad nespadne + +`en.ts` je `Partial`, takze **nemusi byt uplny**. Co v nem neni, spadne +na cestinu: + +```ts +const text = dictionaries[locale][key] ?? cs[key] ?? key; +``` + +Diky tomu jde preklad doplnovat po castech. Kdyby anglictina musela byt uplna, +znamenal by kazdy novy cesky text rozbity build, dokud ho nekdo neprelozi - +a vysledkem by bylo, ze se texty nepridavaji. + +Cestina je zaroven zaloha, takze nedokoncena anglictina znamena misty cestinu. +To je horsi nez hotovy preklad, ale lepsi nez prazdno nebo `hero.title` +v nadpisu. + +## Co uz je prelozene + +| Cast | Stav | +| ----------------------------------- | ------------------------------------ | +| Hlavicka a mobilni menu | hotovo | +| Hero vcetne snimku portalu | hotovo | +| Pas s logy klientu | hotovo | +| Cisla | hotovo | +| Zaverecna vyzva | hotovo | +| Paticka | castecne, sloupce odkazu zatim cesky | +| Sekce Produkty, Reference, Postup | ne | +| Stranky O nas, Sluzby, Kontakt, 404 | ne | +| Portal za prihlasenim | ne | + +Prevod zbytku je mechanicky: text ven do `cs.ts`, klic do `en.ts`, v komponente +`const t = useT()` a `{t('klic')}`. Zadna dalsi prace na vrstve uz potreba neni. + +## Co se neprekada + +Jmeno znacky, kontakty, ICO a adresa zustavaji v `config/brand.ts`. Nemeni se +s jazykem a mit je ve slovniku dvakrat by znamenalo, ze se jednou zmeni jen +jedno misto. + +Claim se **prekada** (`brand.claim`), protoze to je text, ne udaj. + +## Co chybi + +| Chybi | Poznamka | +| ---------------------- | ----------------------------------------------------------------- | +| Prevod zbytku obsahu | viz tabulka vys | +| Jazyk v adrese | `/en/sluzby` misto volby v prohlizeci. Chce to kvuli vyhledavacum | +| Preklad dat ze serveru | nazvy sluzeb, stavu a chybovych hlasek chodi z API cesky | +| Format cisel a datumu | `toLocaleString` se zatim vola natvrdo s `cs-CZ` | diff --git a/documentation/99-zmeny.md b/documentation/99-zmeny.md index e81cdcc..c1269c4 100644 --- a/documentation/99-zmeny.md +++ b/documentation/99-zmeny.md @@ -2,6 +2,36 @@ Nejnovejsi nahore. +## 2026-08-26 - jazyky, skutecny snimek v heru a opravene titulky + +### Pridano + +- **Prepinani jazyku** (`web/src/i18n/`). Vybrany jazyk je jedna hodnota pro + celou aplikaci, drzena mimo React - tentyz vzorec jako u vybrane firmy. + Volba prezije obnoveni stranky a prepisuje ``. Anglicky slovnik + je `Partial`: co v nem neni, spadne na cestinu, takze preklad jde doplnovat + po castech misto jednoho velkeho skoku. Podrobnosti + v [23-jazyky.md](23-jazyky.md). +- **Prepinac jazyka** v hlavicce a v mobilnim menu. + +### Zmeneno + +- **Snimek v heru vypada jako portal doopravdy.** Byl svetly a ukazoval + obrazovku, ktera v produktu neexistuje. Ted ma tmavy podklad, tytez zalozky + vcetne prepinace firmy, tytez sloupce tabulky i tytez odznaky stavu jako + stranka Tickety. Kdyz se navstevnik prihlasi a uvidi neco jineho, prvni + dojem z produktu je, ze web lhal. +- **Jmeno znacky si sklada `usePageMeta`, ne stranky.** Bylo napsane natvrdo + ve dvaceti ctyrech titulcich, takze prejmenovani produktu zmenilo + `brand.ts` a titulky ne - hlavicka rikala nove jmeno a zalozka v prohlizeci + stare. + +### Opraveno + +- **Odkaz "Prohlednout si to" vedl mimo proxy.** Byl to ``, + tedy koren domeny, ne `/apps//sluzby`. Ted je to `Link` z routeru + jako vsude jinde. Byl to jediny takovy odkaz v celem webu. + ## 2026-08-26 - na aplikaci je videt, ktery build to je Nasazena aplikace o dva commity pozadu funguje a vypada skoro stejne jako diff --git a/web/src/components/home/CallToAction.tsx b/web/src/components/home/CallToAction.tsx index b691cc2..f800c1d 100644 --- a/web/src/components/home/CallToAction.tsx +++ b/web/src/components/home/CallToAction.tsx @@ -2,9 +2,12 @@ import { ArrowRight, Mail, Phone } from 'lucide-react'; import { ButtonLink } from '@/components/ui/Button'; import { Container } from '@/components/ui/Container'; import { brand } from '@/config/brand'; +import { useT } from '@/i18n'; /** Zaverecna vyzva k akci pred paticku. */ export function CallToAction() { + const t = useT(); + return (
@@ -14,16 +17,15 @@ export function CallToAction() { className="animate-pulse-slow pointer-events-none absolute -top-24 left-1/2 size-96 -translate-x-1/2 rounded-full bg-brand-500/20 blur-[100px]" />

- Máte proces, který vás žere? Pojďme se na něj podívat. + {t('cta.titleFirst')} {t('cta.titleAccent')}

- Půlhodinový hovor, ve kterém si řekneme, co se dá automatizovat hned, co později a co - nemá smysl vůbec. Bez závazku a bez prezentací na 40 slidů. + {t('cta.lead')}

- Napsat nám + {t('cta.write')} diff --git a/web/src/components/home/Hero.tsx b/web/src/components/home/Hero.tsx index dbe7f09..28402a3 100644 --- a/web/src/components/home/Hero.tsx +++ b/web/src/components/home/Hero.tsx @@ -1,18 +1,29 @@ import { ArrowRight, + Blocks, + Building2, ChevronDown, + FileInput, + LayoutDashboard, + LifeBuoy, + Mail, + MessageCircle, + MessageSquarePlus, Play, + Plug, Plus, Search, Settings2, Share2, - SlidersHorizontal, Users, + Workflow, Zap, } from 'lucide-react'; +import { Link } from 'react-router-dom'; import { ButtonLink } from '@/components/ui/Button'; import { Container } from '@/components/ui/Container'; import { LogoMark } from '@/components/layout/Logo'; +import { useT, type MessageKey } from '@/i18n'; /** * Hero. @@ -20,35 +31,21 @@ import { LogoMark } from '@/components/layout/Logo'; * Zacina prikazem a slibem, ne pozdravem a predstavenim firmy. Prvni je * o zakaznikovi, druhe o nas - a na hlavni stranu patri to prvni. * - * Slozeni: proklada nadtitulek, dvoubarevny nadpis, jedno primarni tlacitko + * Slozeni: prokladany nadtitulek, dvoubarevny nadpis, jedno primarni tlacitko * plus textovy odkaz, ctyri slovesa a snimek portalu vybihajici z prave hrany. */ /** Ctyri kroky, ktere produkt opravdu dela. Nazvy sedi na zalozky v portalu. */ -const steps = [ - { - icon: Zap, - title: 'Sbírá', - text: 'Události ze všech vašich nástrojů na jednom místě.', - }, - { - icon: Settings2, - title: 'Automatizuje', - text: 'Odbaví, co jde odbavit bez člověka.', - }, - { - icon: Share2, - title: 'Propojuje', - text: 'Spustí navazující kroky a celé workflow.', - }, - { - icon: Users, - title: 'Deleguje', - text: 'Zapojí správné lidi, když je potřeba.', - }, +const steps: Array<{ icon: typeof Zap; title: MessageKey; text: MessageKey }> = [ + { icon: Zap, title: 'hero.step1.title', text: 'hero.step1.text' }, + { icon: Settings2, title: 'hero.step2.title', text: 'hero.step2.text' }, + { icon: Share2, title: 'hero.step3.title', text: 'hero.step3.text' }, + { icon: Users, title: 'hero.step4.title', text: 'hero.step4.text' }, ]; export function Hero() { + const t = useT(); + return ( /* Zelena zare je pozadi **sekce**, ne vrstva pod ni. @@ -78,39 +75,41 @@ export function Hero() {

- Události dovnitř. Akce ven. + {t('hero.kicker')}

- Konec + {t('hero.titleFirst')}
- ruční práce. + {t('hero.titleAccent')}

- WorkNuke sbírá události, zautomatizuje, co jde, propojí další kroky a zapojí - správné lidi, když je potřeba. Vaše práce prostě plyne. + {t('hero.lead')}

@@ -124,8 +123,8 @@ export function Hero() { {steps.map((step) => (
  • -

    {step.title}

    -

    {step.text}

    +

    {t(step.title)}

    +

    {t(step.text)}

  • ))} @@ -139,196 +138,220 @@ export function Hero() { /** * Snimek portalu. * - * Vybiha z prave hrany, protoze oriznuty snimek pusobi jako vyrez z neceho - * vetsiho, kdezto cely v ramecku pusobi jako obrazek. Data jsou ilustracni, - * ale nazvy sloupcu i stavy jsou ty, ktere portal opravdu ma. + * **Musi vypadat jako portal doopravdy**, ne jako hezky obrazek vedle nej. + * Kdyz se navstevnik prihlasi a uvidi neco jineho, prvni dojem z produktu je, + * ze web lhal. Proto tmavy podklad, tytez zalozky vcetne prepinace firmy, + * tytez sloupce tabulky i tytez odznaky stavu jako na strance Tickety. + * + * Vybiha z prave hrany: oriznuty snimek pusobi jako vyrez z neceho vetsiho, + * cely v ramecku pusobi jako obrazek. */ -const rows = [ - { - event: 'Platba selhala', - detail: 'Nepodařilo se strhnout platbu', - source: 'Stripe', - color: 'bg-[#635bff]', - status: 'Automatizováno', - tone: 'bg-lime-100 text-lime-800', - next: 'Zkusit znovu za hodinu', - time: 'před 2 min', - }, - { - event: 'Nový požadavek', - detail: 'Problém s přihlášením', - source: 'Zendesk', - color: 'bg-[#03363d]', - status: 'Delegováno', - tone: 'bg-indigo-100 text-indigo-800', - next: 'Přiřazeno Petrovi', - time: 'před 12 min', - }, - { - event: 'Vysoké vytížení CPU', - detail: 'Nad 90 % po 5 minut', - source: 'Datadog', - color: 'bg-[#632ca6]', - status: 'Probíhá', - tone: 'bg-sky-100 text-sky-800', - next: 'Spustit nápravu', - time: 'před 28 min', - }, - { - event: 'Nová registrace', - detail: 'Zkušební účet vytvořen', - source: 'Intercom', - color: 'bg-[#1f8ded]', - status: 'Automatizováno', - tone: 'bg-lime-100 text-lime-800', - next: 'Odeslat uvítání', - time: 'před 1 hod', - }, - { - event: 'Odeslán formulář', - detail: 'Poptávka z webu', - source: 'HubSpot', - color: 'bg-[#ff7a59]', - status: 'Otevřené', - tone: 'bg-orange-100 text-orange-800', - next: 'Kvalifikovat lead', - time: 'před 2 hod', - }, +const panelNav: Array<{ icon: typeof Zap; label: MessageKey }> = [ + { icon: LayoutDashboard, label: 'panel.nav.overview' }, + { icon: LifeBuoy, label: 'panel.nav.tickets' }, + { icon: MessageSquarePlus, label: 'panel.nav.helpdesk' }, + { icon: Users, label: 'panel.nav.people' }, + { icon: Workflow, label: 'panel.nav.automations' }, + { icon: Blocks, label: 'panel.nav.services' }, + { icon: Plug, label: 'panel.nav.connectors' }, ]; -const nav = [ - 'Přehled', - 'Události', - 'Automatizace', - 'Workflow', - 'Lidé', - 'Integrace', - 'Analytika', - 'Nastavení', +const tones = { + brand: 'border-brand-400/35 bg-brand-500/12 text-brand-300', + warn: 'border-warn-400/35 bg-warn-500/12 text-warn-400', + ok: 'border-ok-400/35 bg-ok-500/12 text-ok-400', + neutral: 'border-ink-600/70 bg-ink-700/50 text-white/55', +}; + +const panelRows: Array<{ + id: string; + subject: MessageKey; + customer: string; + channel: MessageKey; + channelIcon: typeof Zap; + status: MessageKey; + tone: keyof typeof tones; + /** Jmeno cloveka se neprekada, "ve fronte" ano - proto oboji. */ + assignee: string; + assigneeKey?: MessageKey; + time: MessageKey; +}> = [ + { + id: 'TK-4821', + subject: 'panel.row1.subject', + customer: 'Nordis a.s.', + channel: 'panel.row1.channel', + channelIcon: Mail, + status: 'panel.row1.status', + tone: 'brand' as const, + assignee: 'Petr N.', + time: 'panel.ago2min', + }, + { + id: 'TK-4820', + subject: 'panel.row2.subject', + customer: 'LogiTrans', + channel: 'panel.row2.channel', + channelIcon: FileInput, + status: 'panel.row2.status', + tone: 'warn' as const, + assignee: 'Eva N.', + time: 'panel.ago24min', + }, + { + id: 'TK-4819', + subject: 'panel.row3.subject', + customer: 'MediPoint', + channel: 'panel.row3.channel', + channelIcon: MessageCircle, + status: 'panel.row3.status', + tone: 'neutral' as const, + assignee: '', + assigneeKey: 'panel.queue', + time: 'panel.ago1h', + }, + { + id: 'TK-4818', + subject: 'panel.row4.subject', + customer: 'Bistro Kolektiv', + channel: 'panel.row4.channel', + channelIcon: LayoutDashboard, + status: 'panel.row4.status', + tone: 'ok' as const, + assignee: 'Karel V.', + time: 'panel.ago3h', + }, ]; function HeroPanel() { + const t = useT(); + return ( - /* - Snimek musi vybihat z hrany na kazde sirce, ne jen na notebooku. - Pevny zaporny okraj ve vw prestal stacit nad 1600 px: kontejner ma strop - max-w-6xl (72rem), takze postranni mezera roste a snimek se do ni vesel. - Vypocet ji odecte, at je obrazovka jakkoliv siroka. - */
    -
    +
    - {/* Postranni navigace */} -