Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ node_modules/
.ignore/
dist/
.next/
.plasmo/
.env*.local
messages.json
sitemap.xml
15 changes: 8 additions & 7 deletions apps/website/next.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
// TODO: Bug in Next.js 15.4.x, cannot upgrade, see https://github.com/vercel/next.js/issues/81628

import { fetchAllRuntimes } from '@evaluate/runtimes';

/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
Expand All @@ -15,11 +13,10 @@ const nextConfig = {
permanent: false,
},
{
source: `/:slug(${(await fetchAllRuntimes())
.map((r) => r.id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
.join('|')})`,
destination: '/playgrounds/:slug',
permanent: true,
// (?:[a-z]{2,3}(-[A-Z][a-z]+)?(-[A-Z]{2}|\d{3})?)
source: '/:locale((?:[a-zA-Z]{2})(?:-[a-zA-Z]{2})?)',
destination: '/:locale/playgrounds',
permanent: false,
},
];
},
Expand Down Expand Up @@ -52,6 +49,10 @@ const nextConfig = {
},
],
},

experimental: {
swcPlugins: [['@sayable/swc-plugin', {}]],
},
};

const truthy = (v) => ['true', 't', '1'].includes(v);
Expand Down
10 changes: 10 additions & 0 deletions apps/website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
"type": "module",
"scripts": {
"check": "tsc --noEmit",
"prebuild": "sayable extract && sayable compile",
"build": "use-env -p NEXT -P -- next build --no-lint",
"postbuild": "use-env -p NEXT -P -- next-sitemap --config sitemap.config.ts",
"start": "use-env -p NEXT -P -- next start",
"dev": "use-env -p NEXT -- next dev --turbo"
},
Expand All @@ -19,6 +21,7 @@
"@evaluate/runtimes": "workspace:^",
"@evaluate/style": "workspace:^",
"@hookform/resolvers": "^5.1.1",
"@sayable/react": "0.0.0-alpha.8",
"@t3-oss/env-nextjs": "^0.13.8",
"@tanstack/react-query": "^5.83.0",
"@tanstack/react-query-devtools": "^5.83.0",
Expand Down Expand Up @@ -46,6 +49,8 @@
"react-dom": "^19.1.0",
"react-hook-form": "^7.60.0",
"react-hotkeys-hook": "^5.1.0",
"sayable": "0.0.0-alpha.6",
"server-only": "^0.0.1",
"sharp": "^0.34.3",
"tailwind-merge": "^3.3.1",
"type-fest": "^4.41.0",
Expand All @@ -56,10 +61,15 @@
"devDependencies": {
"@million/lint": "^1.0.14",
"@next/bundle-analyzer": "15.3.5",
"@sayable/config": "0.0.0-alpha.4",
"@sayable/factory": "0.0.0-alpha.6",
"@sayable/format-po": "0.0.0-alpha.4",
"@sayable/swc-plugin": "0.0.0-alpha.5",
"@tailwindcss/postcss": "^4.1.11",
"@types/file-saver": "^2.0.7",
"@types/react": "^19.1.8",
"autoprefixer": "^10.4.21",
"next-sitemap": "^4.2.3",
"tailwindcss": "^4.1.11"
}
}
12 changes: 12 additions & 0 deletions apps/website/sayable.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { defineConfig } from '@sayable/config';

export default defineConfig({
sourceLocale: 'en',
locales: ['en', 'fr'],
catalogues: [
{
include: ['src/**/*.{ts,tsx}'],
output: 'src/locales/{locale}/messages.{extension}',
},
],
});
21 changes: 21 additions & 0 deletions apps/website/sitemap.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { IConfig } from 'next-sitemap';
import sayable from './sayable.config.ts';

export default {
siteUrl: process.env.NEXT_PUBLIC_WEBSITE_URL!,
generateIndexSitemap: false,

transform: (_: unknown, pathname: string) => {
if (!pathname.startsWith('/en/')) return undefined;
return {
loc: pathname.replace('/en/', '/'),
lastmod: new Date().toISOString(),
changefreq: 'weekly',
priority: 0.7,
alternateRefs: sayable.locales.map((locale) => ({
href: `${process.env.NEXT_PUBLIC_WEBSITE_URL!}/${locale}${pathname.replace('/en/', '/')}`,
hreflang: locale,
})),
};
},
} satisfies IConfig;
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import { Button } from '@evaluate/components/button';
import Link from 'next/link';
import { Say } from '@sayable/react';
import { LocalisedLink } from '~/components/localised-link';

export default function PlaygroundNotFound() {
return (
<div className="mt-[20vh] flex flex-col items-center justify-center">
<span className="font-bold text-8xl">404</span>
<h1 className="font-bold text-4xl text-primary">Not Found</h1>
<h1 className="font-bold text-4xl text-primary">
<Say>Not Found</Say>
</h1>
<p className="text-balance text-center text-muted-foreground">
Hmm, we couldn't find the playground you're looking for.
<Say>Hmm, we couldn't find the playground you're looking for.</Say>
</p>

<div className="mt-2">
<Button variant="secondary" asChild>
<Link href="/playgrounds">Browse Playgrounds</Link>
<LocalisedLink href="/playgrounds">
<Say>Browse Playgrounds</Say>
</LocalisedLink>
</Button>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { fetchAllRuntimes, fetchRuntimeById } from '@evaluate/runtimes';
import { notFound } from 'next/navigation';
import { generateBaseMetadata } from '~/app/metadata';
import { generateBaseMetadata } from '~/app/[locale]/metadata';

import { Editor } from '~/components/editor';
import { Explorer } from '~/components/explorer';
import { ExplorerProvider } from '~/components/explorer/use';
import { Terminal } from '~/components/terminal';
import { TerminalProvider } from '~/components/terminal/use';
import say from '~/i18n';
import type { PageProps } from '~/types';
import { EditorWrapper } from './wrapper';

Expand All @@ -14,21 +16,28 @@ export async function generateStaticParams() {
return runtimes.map((r) => ({ playground: r.id }));
}

export async function generateMetadata(props: PageProps<['[playground]']>) {
const playground = (await props.params).playground;
export async function generateMetadata(
props: PageProps<['[locale]', '[playground]']>,
) {
const { locale, playground } = await props.params;
const runtime = await fetchRuntimeById(decodeURIComponent(playground));
if (!runtime) notFound();

return generateBaseMetadata(`/playground/${playground}`, {
title: `${runtime.name} Playground on Evaluate`,
description: `Run code in ${runtime.name} and other programming languages effortlessly with Evaluate. Input your code, optional arguments, and get instant results. Debug, optimize, and elevate your coding experience with our versatile evaluation tools.`,
keywords: [runtime.name, ...runtime.aliases, ...runtime.tags] //
.map((k) => k.toLowerCase()),
});
return generateBaseMetadata(
say.activate(locale),
`/playgrounds/${playground}`,
(say) => ({
title: say`${runtime.name} Online Playground on Evaluate`,
description: say`Run ${runtime.name} and more code snippets online in your browser with Evaluates code playground.`,
keywords: [runtime.name, ...runtime.aliases, ...runtime.tags].map((k) =>
k.toLowerCase(),
),
}),
);
}

export default async function EditorPage(props: PageProps<['[playground]']>) {
const playground = (await props.params).playground;
const { playground } = await props.params;
const runtime = await fetchRuntimeById(decodeURIComponent(playground));
if (!runtime) notFound();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { fetchAllRuntimes } from '@evaluate/runtimes';
import { generateBaseMetadata } from '~/app/metadata';
import { Say } from '@sayable/react';
import say from '~/i18n';
import type { PageProps } from '~/types';
import { generateBaseMetadata } from '../../metadata';
import { PlaygroundCardList } from './playground-card-list';

export function generateMetadata() {
return generateBaseMetadata('/playgrounds');
export async function generateMetadata(props: PageProps<['[locale]']>) {
const { locale } = await props.params;
return generateBaseMetadata(say.activate(locale), '/playgrounds');
}

export default async function PlaygroundsPage() {
Expand All @@ -13,13 +17,16 @@ export default async function PlaygroundsPage() {
<div className="container flex flex-col gap-6 py-6">
<div className="space-y-6 py-24 text-center">
<h1 className="font-bold text-3xl text-primary tracking-tight md:text-5xl">
Playgrounds
<Say>Playgrounds</Say>
</h1>
<p className="text-balance text-sm md:text-base">
Explore and run code in different programming languages and runtimes.
<Say>
Explore and run code in different programming languages and
runtimes.
</Say>
<br />
<span className="opacity-70">
Powered by the Piston execution engine.
<Say>Powered by the Piston execution engine.</Say>
</span>
</p>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ import {
import { Separator } from '@evaluate/components/separator';
import { toast } from '@evaluate/components/toast';
import type { Runtime } from '@evaluate/runtimes';
import { Say, useSay } from '@sayable/react';
import {
ArrowDownWideNarrowIcon,
CircleDotIcon,
SearchIcon,
XIcon,
} from 'lucide-react';
import { useDeferredValue, useEffect, useMemo } from 'react';
import type { Sayable } from 'sayable';
import { useHashFragment } from '~/hooks/hash-fragment';
import { useLocalStorage } from '~/hooks/local-storage';
import { useQueryParameter } from '~/hooks/query-parameter';
Expand All @@ -30,6 +32,8 @@ export function PlaygroundCardList({
}: {
initialRuntimes: Runtime[];
}) {
const say = useSay() as Sayable;

const [search, setSearch] = useQueryParameter('search');
const deferredSearch = useDeferredValue(search);
const searchedRuntimes = useMemo(() => {
Expand Down Expand Up @@ -73,10 +77,10 @@ export function PlaygroundCardList({
const [hash] = useHashFragment();
useEffect(() => {
if (hash)
toast.info('Choose a playground!', {
toast.info(say`Choose a playground!`, {
icon: <CircleDotIcon className="size-4" />,
});
}, [hash]);
}, [say, hash]);

return (
<div className="space-y-3">
Expand All @@ -86,7 +90,7 @@ export function PlaygroundCardList({

<Input
className="absolute inset-0 h-full w-full pl-7"
placeholder="Search runtime playgrounds..."
placeholder={say`Search runtime playgrounds...`}
value={search ?? ''}
onChange={(e) => setSearch(e.target.value)}
/>
Expand All @@ -97,21 +101,33 @@ export function PlaygroundCardList({
className="absolute top-[27%] right-2 size-4 cursor-pointer opacity-50"
onClick={() => setSearch('')}
>
<span className="sr-only">Clear Search</span>
<span className="sr-only">
<Say>
<Say>Clear Search</Say>
</Say>
</span>
</XIcon>
)}
</div>

<Select value={sortBy} onValueChange={setSortBy as never}>
<SelectTrigger className="w-[205px]" icon={ArrowDownWideNarrowIcon}>
<SelectValue placeholder="Sort by..." />
<span className="sr-only">Toggle Sort By Dropdown</span>
<SelectValue placeholder={say`Sort by...`} />
<span className="sr-only">
<Say>Toggle Sort By Dropdown</Say>
</span>
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>Sort By</SelectLabel>
<SelectItem value="popularity">Popularity</SelectItem>
<SelectItem value="name">Name</SelectItem>
<SelectLabel>
<Say>Sort By</Say>
</SelectLabel>
<SelectItem value="popularity">
<Say>Popularity</Say>
</SelectItem>
<SelectItem value="name">
<Say>Name</Say>
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import {
CardTitle,
} from '@evaluate/components/card';
import { getRuntimeIconUrl, type Runtime } from '@evaluate/runtimes';
import { Say } from '@sayable/react';
import { CodeIcon, PinIcon } from 'lucide-react';
import Link from 'next/link';
import { useCallback, useMemo, useRef, useState } from 'react';
import { ImageWithFallback } from '~/components/image-fallback';
import { LocalisedLink } from '~/components/localised-link';
import { useLocalStorage } from '~/hooks/local-storage';
import { getDominantColour, type RGB } from './get-colour';

Expand Down Expand Up @@ -72,14 +73,16 @@ export function PlaygroundCard({
<CardDescription>v{runtime.version}</CardDescription>
</CardHeader>

<Link
<LocalisedLink
suppressHydrationWarning
className="absolute inset-0"
href={`/playgrounds/${runtime.id}${hash ? `#${hash}` : ''}`}
prefetch={false}
>
<span className="sr-only">{`Open ${runtime.name} Playground`}</span>
</Link>
<span className="sr-only">
<Say>Open {runtime.name} Playground</Say>
</span>
</LocalisedLink>

<div className="absolute top-0 right-0">
<Button
Expand Down
Loading