Skip to content

Feat/status page i18n#1966

Open
aggmoulik wants to merge 16 commits intoopenstatusHQ:mainfrom
aggmoulik:feat/status-page-i18n
Open

Feat/status page i18n#1966
aggmoulik wants to merge 16 commits intoopenstatusHQ:mainfrom
aggmoulik:feat/status-page-i18n

Conversation

@aggmoulik
Copy link
Contributor

No description provided.

@vercel
Copy link

vercel bot commented Mar 11, 2026

@aggmoulik is attempting to deploy a commit to the OpenStatus Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds internationalization (i18n) support to the status-page app using next-intl, introducing locale-aware routing, translated UI strings, and new localized public/auth pages (events, monitors, verify/unsubscribe/manage, feeds, badges).

Changes:

  • Integrates next-intl (config, request routing, Next.js plugin) and adds locale-prefixed route structure under /[domain]/[locale]/....
  • Replaces hard-coded UI strings with next-intl lookups across key status-page components and nav.
  • Adds new localized pages and routes for monitors, events, feeds (RSS/Atom/JSON), verification/unsubscribe, and badges; introduces static components used by the public playground.

Reviewed changes

Copilot reviewed 42 out of 64 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
pnpm-lock.yaml Lockfile updates for next-intl + transitive deps (incl. SWC-related).
apps/status-page/package.json Adds next-intl dependency.
apps/status-page/next.config.ts Wraps Next config with next-intl plugin; adds locale-aware subdomain rewrites.
apps/status-page/src/i18n/config.ts Defines supported locales + default locale.
apps/status-page/src/i18n/routing.ts Adds next-intl routing config.
apps/status-page/src/i18n/request.ts Adds request-time locale/messages loading for next-intl.
apps/status-page/src/proxy.ts Middleware-like proxy: locale detection/redirects + auth gating + rewrites.
apps/status-page/src/hooks/use-pathname-prefix.ts Makes link prefix generation locale-aware.
apps/status-page/src/components/language-switcher.tsx Adds locale switcher UI in footer.
apps/status-page/src/components/nav/header.tsx Translates nav labels and some toast text.
apps/status-page/src/components/nav/footer.tsx Adds language switcher; translates “powered by”.
apps/status-page/src/components/status-page/status-updates.tsx Translates subscription UI strings.
apps/status-page/src/components/status-page/status-tracker.tsx Translates tracker labels/status text.
apps/status-page/src/components/status-page/status-monitor.tsx Translates monitor status/footer text.
apps/status-page/src/components/status-page/status-feed.tsx Translates empty state + CTA copy.
apps/status-page/src/components/status-page/status-events.tsx Translates status/update labels and CTAs.
apps/status-page/src/components/status-page/status-banner.tsx Translates banner “system status” copy; removes old messages import.
apps/status-page/src/components/status-page/messages.ts Removes legacy hard-coded message dictionaries (now handled via i18n).
apps/status-page/src/components/status-page/static/status-banner-static.tsx Adds “static” banner component (used in playground).
apps/status-page/src/components/status-page/static/status-monitor-static.tsx Adds “static” monitor component (used in playground).
apps/status-page/src/components/status-page/static/status-tracker-static.tsx Adds “static” tracker component (used in playground).
apps/status-page/src/components/status-page/static/status-events-static.tsx Adds “static” events timeline component (used in playground).
apps/status-page/src/components/forms/form-email.tsx Translates form labels + toast states.
apps/status-page/src/components/forms/form-password.tsx Translates form labels + toast states.
apps/status-page/src/components/forms/form-subscribe-email.tsx Translates subscribe form labels + toast states + empty states.
apps/status-page/src/components/forms/form-manage-subscription.tsx Translates manage-subscription form strings.
apps/status-page/src/app/(public)/client.tsx Uses new static status components in the public playground.
apps/status-page/src/app/(status-page)/[domain]/[locale]/layout.tsx Adds locale layout with NextIntlClientProvider + message loading.
apps/status-page/src/app/(status-page)/[domain]/[locale]/(public)/layout.tsx Adds shared public layout with Header/Footer.
apps/status-page/src/app/(status-page)/[domain]/[locale]/(public)/page.tsx Adds localized main status-page entry route.
apps/status-page/src/app/(status-page)/[domain]/[locale]/(public)/events/layout.tsx Adds events section layout (loads page header).
apps/status-page/src/app/(status-page)/[domain]/[locale]/(public)/events/(list)/search-params.ts Adds nuqs search param parsing for events list tab.
apps/status-page/src/app/(status-page)/[domain]/[locale]/(public)/events/(list)/page.tsx Translates events tabs and empty state strings.
apps/status-page/src/app/(status-page)/[domain]/[locale]/(public)/events/(view)/report/[id]/layout.tsx Prefetches report data for hydration.
apps/status-page/src/app/(status-page)/[domain]/[locale]/(public)/events/(view)/report/[id]/page.tsx Translates “not found” copy for report view.
apps/status-page/src/app/(status-page)/[domain]/[locale]/(public)/events/(view)/maintenance/[id]/layout.tsx Prefetches maintenance data for hydration.
apps/status-page/src/app/(status-page)/[domain]/[locale]/(public)/events/(view)/maintenance/[id]/page.tsx Translates “not found” copy for maintenance view.
apps/status-page/src/app/(status-page)/[domain]/[locale]/(public)/monitors/page.tsx Adds localized monitors listing page.
apps/status-page/src/app/(status-page)/[domain]/[locale]/(public)/monitors/[id]/search-params.ts Adds nuqs search param parsing for monitor detail tabs.
apps/status-page/src/app/(status-page)/[domain]/[locale]/(public)/monitors/[id]/page.tsx Translates monitor detail labels/descriptions and empty state.
apps/status-page/src/app/(status-page)/[domain]/[locale]/(public)/feed/json/route.ts Adds JSON feed route (locale-scoped path).
apps/status-page/src/app/(status-page)/[domain]/[locale]/(public)/feed/[type]/route.ts Adds RSS/Atom feed route.
apps/status-page/src/app/(status-page)/[domain]/[locale]/(public)/badge/route.tsx Adds OG-image badge route.
apps/status-page/src/app/(status-page)/[domain]/[locale]/(public)/badge/v2/route.ts Adds SVG badge route.
apps/status-page/src/app/(status-page)/[domain]/[locale]/(public)/verify/[token]/layout.tsx Adds verify layout (loads page header).
apps/status-page/src/app/(status-page)/[domain]/[locale]/(public)/verify/[token]/page.tsx Adds verify token page UI.
apps/status-page/src/app/(status-page)/[domain]/[locale]/(public)/unsubscribe/[token]/layout.tsx Adds unsubscribe layout (loads page header).
apps/status-page/src/app/(status-page)/[domain]/[locale]/(public)/unsubscribe/[token]/page.tsx Adds unsubscribe flow UI.
apps/status-page/src/app/(status-page)/[domain]/[locale]/(public)/manage/layout.tsx Adds manage section layout (loads page header).
apps/status-page/src/app/(status-page)/[domain]/[locale]/(public)/manage/[token]/layout.tsx Prefetches subscription data for hydration.
apps/status-page/src/app/(status-page)/[domain]/[locale]/(public)/manage/[token]/page.tsx Adds manage subscription UI + unsubscribe dialog.
apps/status-page/src/app/(status-page)/[domain]/[locale]/(auth)/layout.tsx Adds auth layout with page prefetch.
apps/status-page/src/app/(status-page)/[domain]/[locale]/(auth)/login/page.tsx Adds login entry route that selects auth method.
apps/status-page/src/app/(status-page)/[domain]/[locale]/(auth)/login/actions.ts Localizes server action error messages.
apps/status-page/src/app/(status-page)/[domain]/[locale]/(auth)/login/_components/section-password.tsx Localizes password login UI strings.
apps/status-page/src/app/(status-page)/[domain]/[locale]/(auth)/login/_components/section-magic-link.tsx Localizes magic-link login UI strings.
apps/status-page/messages/en.json Adds extracted English messages (hashed keys).
apps/status-page/messages/fr.json Adds extracted French message file (currently empty values).
apps/status-page/messages/de.json Adds extracted German message file (currently empty values).
apps/status-page/messages/es.json Adds extracted Spanish message file (currently empty values).
apps/status-page/messages/pt.json Adds extracted Portuguese message file (currently empty values).
apps/status-page/messages/ja.json Adds extracted Japanese message file (currently empty values).
apps/status-page/messages/zh.json Adds extracted Chinese message file (currently empty values).
apps/status-page/messages/ko.json Adds extracted Korean message file (currently empty values).
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +4 to +8
export const routing = defineRouting({
locales,
defaultLocale,
localePrefix: "never",
});
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

localePrefix: "never" conflicts with the route structure in this PR (all status-page routes are under /[locale]/...) and with the middleware/rewrites that actively insert /en into the URL. This mismatch can cause inconsistent URL generation and redirect loops. Consider using a localePrefix that matches the chosen URL scheme (e.g. always/as-needed) and keep it consistent with middleware and next.config rewrites.

Copilot uses AI. Check for mistakes.
Comment on lines +30 to +41
// Subdomain or custom domain — no domain prefix needed
// But locale prefix is needed for non-default locale
setPrefix(locale !== defaultLocale ? locale : "");
} else {
setPrefix(pathnames[1] || "");
const pathnames = window.location.pathname.split("/");
const domainSegment = pathnames[1] || "";
// Include locale in prefix for non-default locale
if (locale !== defaultLocale) {
setPrefix(`${domainSegment}/${locale}`);
} else {
setPrefix(domainSegment);
}
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hook omits the default locale from the prefix in several cases (e.g. pathname type uses just domainSegment when locale === defaultLocale), but the middleware/rewrites in this PR enforce URLs that include /en. That means internal links like /${prefix}/events will be generated without /en and then immediately redirected. Align link generation with the chosen locale-in-URL strategy (always include default locale, or stop inserting it in middleware/rewrites).

Copilot uses AI. Check for mistakes.
Comment on lines 155 to 159
`${req.nextUrl.origin}${
redirect ?? type === "pathname" ? `/${prefix}` : "/"
redirect ?? type === "pathname"
? `/${prefix}/${currentLocale}`
: `/${currentLocale}`
}`,
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The redirect target for the post-login branch uses redirect ?? type === "pathname" ? ... : ... without parentheses, so it will evaluate the conditional based on (redirect ?? (type === "pathname")) instead of using redirect when present. This causes the redirect query param to be ignored in most cases. Wrap the conditional in parentheses (or build the path in a separate variable) so the logic is redirect ?? (type === "pathname" ? ... : ...) and preserves the provided redirect path.

Copilot uses AI. Check for mistakes.
Comment on lines +62 to +63
console.log("page subdomain", page);

Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This console.log("page subdomain", page) logs the imported DB table object (and the file contains multiple other debug logs). Please remove debug logging or gate it behind a dev-only logger to avoid noisy logs and accidental leakage of request/page information in production.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +3
export const locales = ["en", "fr"] as const;
export type Locale = (typeof locales)[number];
export const defaultLocale: Locale = "en";
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

locales is currently limited to ["en", "fr"], but other parts of this PR (e.g. Next rewrites and the language switcher) reference additional locales (es/de/pt/ja/zh/ko). As written, those locales will fail hasLocale(...) and/or middleware validation and lead to redirects/404s. Align this list with the locales actually supported by routing/rewrites/messages (or constrain those other places to only en/fr).

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +66
// When URL already has a locale prefix (e.g. /fr/events → /subdomain/fr/events)
{
source: "/:locale(en|es|fr|de|pt|ja|zh|ko)/:path*",
has: [
{
type: "host",
value:
process.env.NODE_ENV === "production"
? "(?<subdomain>[^.]+).stpg.dev"
: "(?<subdomain>[^.]+).localhost",
},
],
missing: [
{
type: "header",
key: "x-proxy",
value: "1",
},
{
type: "host",
value:
process.env.NODE_ENV === "production"
? "www.stpg.dev"
: "localhost",
},
],
destination: "/:subdomain/:locale/:path*",
},
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These rewrites hardcode a locale set (en|es|fr|de|pt|ja|zh|ko) and default to /en, but src/i18n/config.ts currently only allows en/fr. Requests rewritten to /.../es/... etc will then hit the [locale] layout and 404. Consider deriving this regex/default from the single source of truth (locales/defaultLocale) or limiting the rewrite to supported locales.

Copilot uses AI. Check for mistakes.
Comment on lines +45 to +56
if (nextLocale === "en") {
if (hasLocaleSegment) {
segments.splice(domainIndex + 1, 1);
}
} else if (hasLocaleSegment) {
segments[domainIndex + 1] = nextLocale;
} else {
segments.splice(domainIndex + 1, 0, nextLocale);
}

router.replace(segments.join("/") || "/");
});
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Locale switching logic is hardcoded to treat "en" as the removable default (if (nextLocale === "en") ...). In this PR the default locale is already defined in i18n/config.ts (defaultLocale), and middleware/rewrites currently insert /en into the URL. Please use defaultLocale here and ensure the switcher’s behavior matches the overall routing strategy (otherwise switching to the default locale will cause an immediate redirect back to a locale-prefixed URL).

Copilot uses AI. Check for mistakes.
@openstatusHQ openstatusHQ deleted a comment from claude bot Mar 11, 2026
aggmoulik and others added 16 commits March 12, 2026 21:32
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace hardcoded strings in status-banner, status-monitor,
status-events, and status-feed with useExtracted() i18n calls.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Moulik Aggarwal <qwertymoulik@gmail.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…and monitor pages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace hardcoded English strings with useExtracted() calls in login
sections, form components, and status updates popover.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Moulik Aggarwal <qwertymoulik@gmail.com>
Remove the old messages.ts file (replaced by useExtracted), update
status-tracker.tsx to use useExtracted, and add a globe-icon language
switcher dropdown in the footer.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update usePathnamePrefix hook to include locale segment in prefix
for non-default locales, ensuring internal links work correctly
across all languages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Turbopack doesn't support .po imports natively. Switch to .json which
next-intl handles out of the box. Create empty JSON catalogs for all
supported locales as placeholders for future translations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Moulik Aggarwal <qwertymoulik@gmail.com>
Signed-off-by: Moulik Aggarwal <qwertymoulik@gmail.com>
Signed-off-by: Moulik Aggarwal <qwertymoulik@gmail.com>
Signed-off-by: Moulik Aggarwal <qwertymoulik@gmail.com>
Signed-off-by: Moulik Aggarwal <qwertymoulik@gmail.com>
Signed-off-by: Moulik Aggarwal <qwertymoulik@gmail.com>
@aggmoulik aggmoulik force-pushed the feat/status-page-i18n branch from 0e25322 to 792216b Compare March 12, 2026 16:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants