feat: playground#2084
Conversation
|
@Adebesin-Cell is attempting to deploy a commit to the next-intl Team on Vercel. A member of the Team first needs to authorize it. |
|
Nice, let me know once it's ready to have a look! |
|
Oh, just one thing after having a quick look: The patterns example I've set up was intended to be the playground. You can just move all your code there! Let's call it "patterns" for now since there is already example-app-router-playground. |
…box redesign + sidebar polish
…put + byline + flat landing cards
… MDX, header offset
|
Hey @amannn 👋 sorry for the long radio silence on #2084, but I'm back on it. I just pushed a full reboot. What’s there now (matches the v1 scope you sketched in issue #1623 #1623 (comment)):
Screenshots attached: landing (en/de, light/dark), both detail pages in both locales, mobile. Ideas for next iteration (would love your read before scoping):
No rush at all, happy to split any of these into separate PRs once v1 feels good 🙏 |
54efbec to
b28d1e5
Compare
…round # Conflicts: # pnpm-lock.yaml
|
Awesome, thanks for your hard work here @Adebesin-Cell! I'll make a note of this and will get back to you! |
|
Hi @amannn, I picked up the work from comment, was curious how those ideas would shape up. Attached a video so you can see how it looks. Also did a quick a11y pass:
Had fun doing this 😅 I’m also thinking of an accessibility/preferences panel similar to how Intl Explorer Playground does theirs, toggles for reduced motion, muting the Explorer’s screen-reader announcements, accent color, etc I have some ideas and would love to include them in the playground as well. Screen.Recording.2026-05-13.at.07.17.56.1.mov |
|
Oh wow @Adebesin-Cell, that's awesome! 😮🔥 I'll get back to you with some more detailed feedback! |
|
Hey @Adebesin-Cell, sorry, I still haven't found the time to dig more into this. But two things:
It'd be good to make sure the foundation and direction is solid first. Then, we can look into adding more pages. But the integration of the |
2634bee to
a14f68f
Compare
|
No worries @amannn 🙏 Did the split — this PR is now scoped to just the Translations foundation. Moved everything else into a Draft follow-up: #2337. It stacks on the same base and contains:
|
amannn
left a comment
There was a problem hiding this comment.
Overall looks really cool, thanks @Adebesin-Cell! 🙌
Here's a first review pass, hopefully there's something useful in this for you. Some remarks are questions to you, let me know what you think!
| import { hasLocale } from 'next-intl'; | ||
| import { routing } from './routing'; | ||
|
|
||
| export default getRequestConfig(async ({ requestLocale }) => { |
…review feedback Relocate the playground into the `example-app-router-patterns` example amannn set up, and scope this PR down to the Translations foundation (Server + Client Components) per review. Addresses the inline feedback on amannn#2084: Structure & config - Move everything from top-level `playground/` into `examples/example-app-router-patterns/`, keeping the existing `/design` reference page (middleware now skips `/design`) - Root `layout.tsx` owns `<html>`/`<body>` + Inter font + providers; the `ThemeProvider` is rendered inline (dropped the `ClientProviders` wrapper) - Adopt the example's conventions: Inter (not Geist), `clsx` (not `cn`), prettier (single quotes, no bracket spacing) - Drop shadcn/ui + `components.json` + `cn`/`tailwind-merge`; the three consumers (sidebar, theme toggle, locale switcher) are now hand-rolled - Remove Playwright tests/config (e2e lives in `/e2e` on main now) and the unused Next template SVGs Styling - globals.css: palette lives directly in `@theme` as `--color-*`; dropped the chart vars and the wholesale palette re-mapping. Only the semantic tokens that flip under `.dark` remain (for runtime theming via next-themes) - Marker + dropdown no longer use the accent color; accent used sparingly - Fix theme-switch flash via `disableTransitionOnChange` Translations pages - Move page headings + nav labels into messages so they're translatable; `lib/nav` now holds message keys instead of hardcoded strings - Single-column MDX (the two-column layout cut off code) - Snippets use `useTranslations` (works in non-async Server Components too), no `setRequestLocale`/`params` noise; server example matches the snippet - Trim Code Hike to the one annotation actually used (`mark`); remove unused annotation handlers, `demo-card`, `two-column`, per-page READMEs Logo & byline - Logo matches the docs (currentColor + padded viewBox, no clipping) - Byline drops the hardcoded credit + uppercase label, keeps Source + Docs
…round # Conflicts: # pnpm-lock.yaml
amannn
left a comment
There was a problem hiding this comment.
Really nice work @Adebesin-Cell! 👏 Thanks also for the helpful replies to my comments!
I've added a few more inline comments, let me know what you think!
| strokeLinecap="square" | ||
| /> | ||
| </g> | ||
| </svg> |
|
|
||
| export function GitHubLink({path}: {path: string}) { | ||
| // Encode segments so dynamic ones like `[locale]` work as a GitHub URL | ||
| const href = `https://github.com/amannn/next-intl/tree/main/${path |
There was a problem hiding this comment.
Can we move the base url to a config file at src/config.tsx? This is also used for byline.tsx.
| } | ||
|
|
||
| /* Subtle dot-grid backdrop for the "output" boundary box */ | ||
| .dotgrid { |
There was a problem hiding this comment.
Coming from #2084 (comment)
Yep, I'd prefer keeping as much styles out of global.css as possible and move to component code. Otherwise this could end up as dead code during a refactor.
|
|
||
| export default async function ClientComponentsPage({params}: Props) { | ||
| const {locale} = await params; | ||
| setRequestLocale(locale); |
There was a problem hiding this comment.
Coming from #2084 (comment): Ok to use requestLocale for now, but I'd already avoid setRequestLocale and just use dynamic rendering for now. Next.js 16.3 with next/root-params was expected for last week already, I think this should be out any minute now.
| const locale = await getLocale(); | ||
|
|
||
| return ( | ||
| <html lang={locale} suppressHydrationWarning className={inter.variable}> |
There was a problem hiding this comment.
This should be part of [locale]/layout.tsx. There's no need for a layout at app/, right?
| import {useLocale} from 'next-intl'; | ||
| import {useParams} from 'next/navigation'; | ||
|
|
||
| export function LocaleSwitcher() { |
| return ( | ||
| <div | ||
| role="group" | ||
| aria-label="Switch language" |
There was a problem hiding this comment.
There are still hardcoded inline labels around, we should switch all consistently to t(…)
| {t('tagline')} | ||
| </p> | ||
| </div> | ||
| <PlaygroundBoundary label={t('examples')} className="space-y-10"> |
There was a problem hiding this comment.
Is it t('examples')? Maybe a generic "pages" could fit better terminology-wise? (also the rendered labels)
| <Link | ||
| href={item.slug} | ||
| key={item.slug} | ||
| className="group bg-background hover:bg-muted flex flex-col gap-1.5 px-5 py-4 transition-colors" |
There was a problem hiding this comment.
hover:bg-muted: I think that's too dark, can we use a lighter shade?
|
|
||
| :root { | ||
| --background: var(--color-gray-50); | ||
| --foreground: var(--color-gray-900); |
There was a problem hiding this comment.
There are two approaches to dark mode:
- Using semantic tokens that switch their internal value (used here)
- Use
dark:class names to declare which specific values a given element has
I know the first one is tempting from an engineering perspective, but it creates a quite heavy abstraction IMO that doesn't allow for much fine-tuning on a case-by-case basis and also a lot of upfront declarations (e.g. specific ones like --sidebar-foreground below).
If you compare what e.g. the Next.js playground does:
bg-gray-900 hover:bg-gray-800
It takes a bit of discipline to apply colors consistently, but I think it's just dead-simple. Reuse of colors is typically achieved through component reuse then (e.g. by using <Prose> where relevant).
I think there was a tweet from Adam from the Tailwind team at some point that he also prefers this approach for the reasons listed here.
Can we switch to this simpler inline approach to remove another architectural layer and reduce complexity?


Closes #1623.
Reboot of the playground refactor with the v1 scope agreed in #1623 (comment): a single category (Translations) with two detail pages (Server / Client Components), MDX-driven content via Code Hike, locale-prefixed routing, and the labeled-boundary visual identity from the Vercel app-router playground applied to next-intl's design system.
What's in this PR
playground/workspace (Next 15.5.15, React 19, Tailwind v4, shadcn primitives)next-intlmiddleware +[locale]routing withen/demessages@next/mdx+ Code Hike pipeline (remarkCodeHike+recmaCodeHike) wired innext.config.ts, with a custom<Code>RSC and 6 annotation handlers (mark, callout, focus, link, fold, line-numbers)bg-backgroundcorner labels punching through hairline borderscontent.mdx+ a sibling live demo component:getTranslations+setRequestLocaleuseTranslationswith an interactive ICU{name}interpolation<PlaygroundBoundary label="Demo">+ a separate<PlaygroundBoundary label="Output" variant="dotgrid">so the rendered demo reads like a render previewcoming soonplaceholder so adding them later is just a data change insrc/lib/nav.tsexamples/example-app-router-playgroundis untouched — e2e coverage stays where it lives until the new playground absorbs it in a follow-upScreenshots
Landing — dark
Landing — light
Client Components — dark
Client Components — light
Out of scope (explicit)
intl-explorer-style interactive formatting playgroundtabs/CodeWithNotes(no v1 page needs them)examples/example-app-router-playgroundTry it
Open http://localhost:3000 — middleware redirects to
/en, then click into either Translations page. Locale switcher (top-right) flips to/de, theme toggle persists.Notes for review
@next/mdx's loader options aren't serializable for the current Turbopack pipeline (pnpm dev/builduse webpack)as anycasts innext.config.tswork around a peer-resolved Next 14 / 15 NextConfig type clash from@next/mdxREADME.mdfiles for GitHub-side browsing; currently the README slot just points atcontent.mdx