Conversation
|
@aggmoulik is attempting to deploy a commit to the OpenStatus Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
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-intllookups 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.
| export const routing = defineRouting({ | ||
| locales, | ||
| defaultLocale, | ||
| localePrefix: "never", | ||
| }); |
There was a problem hiding this comment.
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.
| // 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); | ||
| } |
There was a problem hiding this comment.
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).
| `${req.nextUrl.origin}${ | ||
| redirect ?? type === "pathname" ? `/${prefix}` : "/" | ||
| redirect ?? type === "pathname" | ||
| ? `/${prefix}/${currentLocale}` | ||
| : `/${currentLocale}` | ||
| }`, |
There was a problem hiding this comment.
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.
| console.log("page subdomain", page); | ||
|
|
There was a problem hiding this comment.
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.
| export const locales = ["en", "fr"] as const; | ||
| export type Locale = (typeof locales)[number]; | ||
| export const defaultLocale: Locale = "en"; |
There was a problem hiding this comment.
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).
| // 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*", | ||
| }, |
There was a problem hiding this comment.
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.
| 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("/") || "/"); | ||
| }); |
There was a problem hiding this comment.
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).
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>
0e25322 to
792216b
Compare
No description provided.