Monorepo for CodeDay's web applications and shared packages.
| Path | Description |
|---|---|
apps/www |
Main CodeDay website (Next.js Pages Router) |
packages/topo |
Shared design system (Chakra UI components, theme, region detection) |
packages/i18n |
Shared internationalization (Paraglide JS messages + runtime) |
packages/utils |
Shared utilities (GraphQL fetch, debug, etc.) |
packages/topocons |
Icon library |
packages/tsconfig |
Shared TypeScript configuration |
pnpm install
pnpm run buildThe monorepo has two independent localization layers. Both are provided by shared packages so any app in the monorepo can use them.
| Layer | Purpose | Provided by | Example values |
|---|---|---|---|
| Locale (language) | UI string translations | packages/i18n |
en, es |
| Region | Domain-specific data (phone numbers, emails, legal) | packages/topo (Region) |
us, eu, uk, ca, in |
Translations are powered by Paraglide JS.
Message files live in packages/i18n/messages/{locale}.json. Keys are prefixed
by package — topo_* for design-system strings, www_* for app strings.
URL structure: Every page URL includes a locale prefix — /en/about,
/es/about. If a user visits a bare path like /about, middleware detects
the browser's preferred language and redirects.
import * as m from "@codeday/i18n/messages";
function Footer() {
return <p>{m.topo_footer_copyright({ currentYear: "2026" })}</p>;
}Client-side (React components):
import { getLocale } from "@codeday/i18n/runtime";
const locale = getLocale(); // "en" | "es"Server-side (getStaticProps / getServerSideProps):
import { getLocaleFromContext } from "@codeday/i18n/next-pages";
export const getStaticProps: GetStaticProps = async (ctx) => {
const locale = getLocaleFromContext(ctx);
// ...
};To use m.*() inside data-fetching functions, wrap with the locale helper:
import { withLocaleStaticProps } from "@codeday/i18n/next-pages";
import * as m from "@codeday/i18n/messages";
export const getStaticProps = withLocaleStaticProps(async (ctx) => {
return { props: { title: m.page_title() } };
});- Add the locale code to
packages/i18n/project.inlang/settings.json→locales - Create
packages/i18n/messages/{locale}.jsonwith translated values - In each app: add the locale to
next.config.ts→i18n.localesandAVAILABLE_LOCALESinmiddleware.ts
| Import path | Contents |
|---|---|
@codeday/i18n/messages |
Type-safe message functions (m.key()) |
@codeday/i18n/runtime |
getLocale(), setLocale(), overwriteGetLocale(), baseLocale, locales, Locale type |
@codeday/i18n/next-pages |
getLocaleFromContext(), withLocaleStaticProps(), withLocaleServerSideProps() |
Region codes are resolved from the top-level domain the visitor is using.
This is independent of UI language — a user on codeday.fr gets region eu
but can still view the site in English.
@codeday/topo/Region ships a built-in TLD_REGION_MAP:
| TLD | Region | TLD | Region | |
|---|---|---|---|---|
.org |
us |
.ee |
eu |
|
.us |
us |
.se |
eu |
|
.ca |
ca |
.it |
eu |
|
.co.uk |
uk |
.fr |
eu |
|
.in |
in |
.es |
eu |
|
.ch |
eu |
|||
.be |
eu |
Apps can pass an optional overrides map to add new TLDs or override
specific full domain names.
- In middleware, resolve and forward the region as a header:
import { getRegionFromHostname, REGION_HEADER } from "@codeday/topo/Region/config";
// inside middleware():
const region = getRegionFromHostname(request.headers.get("host"));
const headers = new Headers(request.headers);
headers.set(REGION_HEADER, region);
return NextResponse.next({ request: { headers } });- In
_app.tsx, wrap with<RegionProvider>:
import { RegionProvider, getRegionFromHostname } from "@codeday/topo/Region";
const region = useMemo(
() => getRegionFromHostname(typeof window !== "undefined" ? window.location.hostname : undefined),
[],
);
return <RegionProvider value={region}>...</RegionProvider>;Pass an overrides map to getRegionFromHostname. Full-domain keys take
priority, then override TLD keys, then the built-in TLD_REGION_MAP.
const overrides = { "staging.codeday.org": "eu", "dev": "us" };
const region = getRegionFromHostname("staging.codeday.org", overrides); // "eu"Client-side (React components):
import { useRegion } from "@codeday/topo/Region";
const region = useRegion(); // "us" | "eu" | "uk" | ...Server-side (getServerSideProps):
import { getRegionFromContext } from "@codeday/topo/Region";
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const region = getRegionFromContext(ctx);
return { props: { region } };
};
getRegionFromContextonly works withgetServerSideProps(it reads request headers). IngetStaticPropsthere is no request — usegetRegionFromHostname()with a known hostname, or resolve the region client-side.
Add an entry to TLD_REGION_MAP in packages/topo/src/Region/config.ts.
No other files need to change.
| Import path | Contents |
|---|---|
@codeday/topo/Region/config |
getRegionFromHostname(), TLD_REGION_MAP, DEFAULT_REGION, REGION_HEADER, RegionMap type — Edge-safe, no React |
@codeday/topo/Region |
Everything from config + RegionProvider, useRegion(), getRegionFromContext() |
_defaultlocale sentinel — Next.js Pages Router requires adefaultLocale. We use_defaultso that bare paths are detectable by middleware (otherwise they'd be silently served as English). Middleware redirects_defaultto the browser's preferred language.@codeday/topo/Region/configvs@codeday/topo/Region— Middleware runs in the Edge runtime, which cannot import React or Node.js built-ins. The/configmodule is kept dependency-free so both middleware and the full app can use it.