From d6a1f60cf2bfe3cfb7ce932a095db82a864ce07c Mon Sep 17 00:00:00 2001 From: Tomasz Stanisz Date: Tue, 6 Oct 2026 08:58:12 +0200 Subject: [PATCH 1/2] Add complete English and Polish portfolio localization --- .gitignore | 3 + .prettierignore | 3 + AGENTS.md | 8 +- README.md | 47 ++- e2e/blog.spec.ts | 2 +- e2e/localization.spec.ts | 357 ++++++++++++++++++ e2e/site.spec.ts | 35 +- next.config.mjs | 2 + playwright.config.ts | 2 +- scripts/prepare-blog.mjs | 7 +- src/app/{ => (en)}/blog/page.tsx | 0 src/app/{ => (en)}/colophon/page.mdx | 0 src/app/(en)/layout.tsx | 6 + src/app/(en)/look/[look]/card.jpg/route.tsx | 10 + src/app/{ => (en)}/look/[look]/page.tsx | 0 src/app/{ => (en)}/page.tsx | 0 src/app/(en)/work/[slug]/page.tsx | 13 + src/app/{ => (pl)}/blog/pl/page.tsx | 0 src/app/(pl)/layout.tsx | 6 + src/app/(pl)/pl/colophon/page.mdx | 64 ++++ .../(pl)/pl/look/[look]/card.jpg/route.tsx | 10 + src/app/(pl)/pl/look/[look]/page.tsx | 48 +++ src/app/(pl)/pl/page.tsx | 6 + src/app/(pl)/pl/work/[slug]/page.tsx | 13 + src/app/global-not-found.tsx | 24 ++ src/app/not-found.tsx | 30 -- src/app/sitemap.ts | 20 +- src/commands.ts | 75 +++- src/components/Blog/BlogArticle.tsx | 9 +- src/components/Blog/BlogIndex.tsx | 7 +- src/components/Blog/BlogList.tsx | 16 +- src/components/Blog/blog.css | 33 -- .../CaseStudy/CaseStudyPage.tsx} | 82 ++-- src/components/CaseStudy/CaseStudyParts.tsx | 10 +- .../CommandPalette/CommandPalette.tsx | 64 +++- .../layout.tsx => components/Document.tsx} | 20 +- src/components/FooterPanel/FooterPanel.tsx | 12 +- src/components/Hero/Hero.tsx | 12 +- .../route.tsx => components/LookCard.tsx} | 91 +++-- src/components/NavBar/LanguageSwitch.tsx | 56 +++ src/components/NavBar/NavBar.tsx | 59 ++- src/components/NavBar/navbar.css | 4 +- src/content/blog/generation.test.ts | 43 ++- src/content/looks.ts | 14 +- src/content/navigation.ts | 21 +- src/content/publicProfile.ts | 5 +- src/content/work/bodies.ts | 9 + src/content/work/orange-cms.pl.mdx | 80 ++++ src/content/work/orange-e2e-testing.pl.mdx | 80 ++++ src/content/work/studies.ts | 65 +++- src/content/work/theeventa-mvp.pl.mdx | 73 ++++ src/design/skin-terminal.css | 10 + src/i18n/locales.ts | 2 + src/i18n/localization.css | 69 ++++ src/i18n/localization.test.ts | 145 +++++++ src/i18n/profile.ts | 190 ++++++++++ src/i18n/routes.ts | 124 ++++++ src/lib/metadata.ts | 33 +- src/lib/structuredData.ts | 24 +- src/sections/Approach/Approach.tsx | 38 +- src/sections/Background/Background.tsx | 63 ++-- src/sections/Contact/Contact.tsx | 36 +- src/sections/HomePage.tsx | 66 ++-- src/sections/Now/NowSection.tsx | 40 +- src/sections/Work/SelectedWork.tsx | 141 +++---- src/utils/scrollToSection.ts | 2 + 66 files changed, 2226 insertions(+), 413 deletions(-) create mode 100644 e2e/localization.spec.ts rename src/app/{ => (en)}/blog/page.tsx (100%) rename src/app/{ => (en)}/colophon/page.mdx (100%) create mode 100644 src/app/(en)/layout.tsx create mode 100644 src/app/(en)/look/[look]/card.jpg/route.tsx rename src/app/{ => (en)}/look/[look]/page.tsx (100%) rename src/app/{ => (en)}/page.tsx (100%) create mode 100644 src/app/(en)/work/[slug]/page.tsx rename src/app/{ => (pl)}/blog/pl/page.tsx (100%) create mode 100644 src/app/(pl)/layout.tsx create mode 100644 src/app/(pl)/pl/colophon/page.mdx create mode 100644 src/app/(pl)/pl/look/[look]/card.jpg/route.tsx create mode 100644 src/app/(pl)/pl/look/[look]/page.tsx create mode 100644 src/app/(pl)/pl/page.tsx create mode 100644 src/app/(pl)/pl/work/[slug]/page.tsx create mode 100644 src/app/global-not-found.tsx delete mode 100644 src/app/not-found.tsx rename src/{app/work/[slug]/page.tsx => components/CaseStudy/CaseStudyPage.tsx} (57%) rename src/{app/layout.tsx => components/Document.tsx} (92%) rename src/{app/look/[look]/card.jpg/route.tsx => components/LookCard.tsx} (88%) create mode 100644 src/components/NavBar/LanguageSwitch.tsx create mode 100644 src/content/work/orange-cms.pl.mdx create mode 100644 src/content/work/orange-e2e-testing.pl.mdx create mode 100644 src/content/work/theeventa-mvp.pl.mdx create mode 100644 src/i18n/locales.ts create mode 100644 src/i18n/localization.css create mode 100644 src/i18n/localization.test.ts create mode 100644 src/i18n/profile.ts create mode 100644 src/i18n/routes.ts diff --git a/.gitignore b/.gitignore index 3c86d8f..ef54361 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,6 @@ workspace.xml /src/content/blog/.generated/ /src/app/blog/en/ /src/app/blog/pl/*/ + +/src/app/(en)/blog/en/ +/src/app/(pl)/blog/pl/*/ diff --git a/.prettierignore b/.prettierignore index 53fac93..f7f4e1d 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,6 @@ src/content/blog/.generated/ src/app/blog/en/ src/app/blog/pl/*/ + +/src/app/(en)/blog/en/ +/src/app/(pl)/blog/pl/*/ diff --git a/AGENTS.md b/AGENTS.md index 0abb7d3..7abfe18 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -36,7 +36,7 @@ All of these must pass before a change is done. CI (`.github/actions/check/`) ru ## Where things live -- `src/app/` — routes. `layout.tsx` is the shell; `page.tsx` is the home page; `colophon/page.mdx` is "How this site is built". +- `src/app/` — routes. `(en)/layout.tsx` and `(pl)/layout.tsx` share the document shell; `page.tsx` is the home page; `colophon/page.mdx` is "How this site is built". - `src/content/publicProfile.ts` — all public wording and links, typed by `Profile`. `src/content/navigation.ts` — the home page sections. - `src/sections/` — the home page sections, rendered as Server Components. - `src/components/NavBar/` — the only client entry point: header, theme and skin controls, and it mounts `CommandPalette/`. @@ -44,16 +44,16 @@ All of these must pass before a change is done. CI (`.github/actions/check/`) ru - `src/utils/` — theme, skin, preferences store, `scrollToSection`, console greeting. - `src/design/skin-terminal.css` — every rule of the optional Terminal skin. - `src/content/work/` — case studies: facts and `published` in `studies.ts`, text in `.mdx`, mapped in `bodies.ts`. Pages render at `/work//`. -- `src/content/looks.ts` and `src/app/look/[look]/` — one copy of the home page per skin and theme, each with a preview card rendered at build time from `card.jpg/route.tsx`. Card colours mirror the CSS tokens; keep them in sync when the design changes. +- `src/content/looks.ts` and `src/app/(en)/look/[look]/` — one copy of the home page per skin and theme, each with a preview card rendered at build time from `card.jpg/route.tsx`. Card colours mirror the CSS tokens; keep them in sync when the design changes. - `src/content/blog/` — typed bilingual registry and approved MDX bodies; `scripts/prepare-blog.mjs` generates ignored static routes and metadata before checks/builds. See README for the private local review workflow. Never copy unreviewed manuscripts into tracked public files or deploy a preview build. - `e2e/site.spec.ts` and `e2e/blog.spec.ts` — Playwright suites; blog routes are discovered from the generated registry. ## Rules that are easy to break -- **CSS order.** Every stylesheet is imported in `src/app/layout.tsx`, and nowhere else, with `skin-terminal.css` last. Its rules are scoped as `:where(html.skin-terminal) …` and win on order, not specificity. A component that imports its own CSS loads after the skin and breaks it. +- **CSS order.** Every stylesheet is imported in `src/components/Document.tsx`, and nowhere else, with `skin-terminal.css` last. Its rules are scoped as `:where(html.skin-terminal) …` and win on order, not specificity. A component that imports its own CSS loads after the skin and breaks it. - **Terminal skin coverage.** New UI needs Terminal styles in `skin-terminal.css`, and must be checked in both skins and both themes. - **Theme and skin state.** Read it with `usePreferences()`, and change it with `applyTheme` or `applySkin`. Don't read `document` or `localStorage` during render, because the server renders without them. -- **Before-paint script.** The inline script in `layout.tsx` applies the theme and skin before paint (share-link `?skin=`/`?theme=` first, then the visit's link choice in `sessionStorage`, then saved choices), and owns the `theme-color` meta tag. Keep its colours in sync with `THEME_COLORS` in `src/utils/theme.ts`. Don't add `themeColor` to Next metadata, because React would duplicate the tag. +- **Before-paint script.** The inline script in `Document.tsx` applies the theme and skin before paint (share-link `?skin=`/`?theme=` first, then the visit's link choice in `sessionStorage`, then saved choices), and owns the `theme-color` meta tag. Keep its colours in sync with `THEME_COLORS` in `src/utils/theme.ts`. Don't add `themeColor` to Next metadata, because React would duplicate the tag. - **Client code.** Keep content in Server Components. Add `"use client"` only where interaction needs it. - **Section links.** In-page section links go through `scrollToSection`, which moves focus to the section's heading. Off the home page they point to `/#id`. - **Per-page metadata.** Every page sets it with `pageMetadata()` from `src/lib/metadata.ts`. New public pages also belong in `src/app/sitemap.ts` (the Playwright suite checks the sitemap matches `PAGES`). diff --git a/README.md b/README.md index d9fed85..a2e82cb 100644 --- a/README.md +++ b/README.md @@ -42,11 +42,11 @@ GitHub Actions runs all of these on Node 24.21.0, through the shared steps in `. ## Active structure -- `src/app/layout.tsx` — the page shell: fonts (`next/font`, self-hosted at build), the inline script that applies a saved theme and skin before first paint, the header and footer, and every stylesheet in cascade order +- `src/components/Document.tsx` — the page shell: fonts (`next/font`, self-hosted at build), the inline script that applies a saved theme and skin before first paint, the header and footer, and every stylesheet in cascade order - `src/app/page.tsx` — the home page, rendered by `src/sections/HomePage.tsx` (shared with the `/look/` copies): the hero, then Work ("Proven in production"), Now, Approach, Background and Contact - `src/app/colophon/page.mdx` — "How this site is built", linked from the footer and the palette. Its performance table is a dated snapshot; re-measure before changing it. - `src/app/work/[slug]/page.tsx` — case study pages. Facts and the published flag live in `src/content/work/studies.ts`, the text in `src/content/work/.mdx` (mapped in `bodies.ts`). A published study gets its page, a link from its card on the home page, and a palette command (`cat work/.md`). Each page has a dark header band with the study's `outcome` tile and a fact strip, an "On this page" list built from the MDX `h2`s (`src/lib/headings.ts`, highlighted by `src/components/CaseStudy/CaseStudyToc.tsx`), and a link to the next study (`nextCaseStudy`). `src/components/Flow/` draws the step diagrams; ``, `