Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

dashboard i18n#942

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Draft
fuwenbin wants to merge3 commits intostack-auth:dev
base:dev
Choose a base branch
Loading
fromfuwenbin:dev
Draft

dashboard i18n#942

fuwenbin wants to merge3 commits intostack-auth:devfromfuwenbin:dev

Conversation

@fuwenbin
Copy link

@fuwenbinfuwenbin commentedOct 10, 2025
edited by recursemlbot
Loading

High-level PR Summary

This PR implements internationalization (i18n) for the Stack Auth dashboard, adding support for English and Chinese (Simplified) languages. It introducesnext-intl for translation management, creates comprehensive translation files with over 1000+ strings, and adds a language switcher component in the navigation bar. Additionally, it includes developer experience improvements with Docker container management scripts (minimal and full configurations), development server utilities, and documentation for local setup. The i18n implementation covers all major dashboard sections including user management, teams, permissions, emails, payments, and settings.

⏱️ Estimated Review Time: 30-90 minutes

💡 Review Order Suggestion
OrderFile Path
1apps/dashboard/src/i18n/routing.ts
2apps/dashboard/src/i18n/request.ts
3apps/dashboard/messages/en.json
4apps/dashboard/messages/zh.json
5apps/dashboard/next.config.mjs
6apps/dashboard/package.json
7apps/dashboard/src/app/layout.tsx
8apps/dashboard/src/components/language-switcher.tsx
9apps/dashboard/src/components/navbar.tsx
10apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
11apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx
12apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/setup-page.tsx
13apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx
14apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx
15apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsx
16apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/[userId]/page-client.tsx
17apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/teams/page-client.tsx
18apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/api-keys/page-client.tsx
19apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/domains/page-client.tsx
20apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/webhooks/page-client.tsx
21apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/emails/page-client.tsx
22apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-templates/page-client.tsx
23apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-themes/page-client.tsx
24apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-drafts/page-client.tsx
25apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/transactions/page-client.tsx
26apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-permissions/page-client.tsx
27apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/team-permissions/page-client.tsx
28apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/team-settings/page-client.tsx
29apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx
30apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx
31apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx
32apps/dashboard/src/components/user-dialog.tsx
33apps/dashboard/src/components/data-table/user-table.tsx
34apps/dashboard/src/components/data-table/team-table.tsx
35apps/dashboard/src/components/data-table/team-member-table.tsx
36apps/dashboard/src/components/data-table/api-key-table.tsx
37apps/dashboard/src/components/data-table/permission-table.tsx
38apps/dashboard/src/components/data-table/transaction-table.tsx
39apps/dashboard/src/components/data-table/payment-product-table.tsx
40apps/dashboard/src/components/data-table/payment-item-table.tsx
41packages/stack-ui/src/components/data-table/data-table.tsx
42packages/stack-ui/src/components/data-table/pagination.tsx
43packages/stack-ui/src/components/data-table/toolbar.tsx
44packages/stack-ui/src/components/data-table/view-options.tsx
45apps/dashboard/src/app/(main)/i18n-test/page.tsx
46docker/dependencies/README.md
47docker/dependencies/docker.compose.minimal.yaml
48scripts/restart-dev-basic.sh
49scripts/kill-dev-servers.sh
50scripts/check-docker.sh
51scripts/fix-docker.sh
52scripts/start-dev.sh
53package.json
54pnpm-lock.yaml
⚠️ Inconsistent Changes Detected
File PathWarning
docker/dependencies/README.mdDocker configuration documentation appears unrelated to the i18n feature - this seems to be a separate infrastructure improvement bundled into the PR
docker/dependencies/docker.compose.minimal.yamlNew minimal Docker compose configuration is unrelated to internationalization - should be in a separate infrastructure/DevEx PR
scripts/restart-dev-basic.shDevelopment server management script is unrelated to i18n functionality
scripts/kill-dev-servers.shDevelopment server cleanup script is unrelated to i18n functionality
scripts/check-docker.shDocker verification script is unrelated to i18n functionality
scripts/fix-docker.shDocker troubleshooting script is unrelated to i18n functionality
scripts/start-dev.shDevelopment environment startup script is unrelated to i18n functionality
package.jsonNew npm scripts for Docker and development management are unrelated to i18n - only the next-intl dependency addition is relevant

Need help? Join our Discord

Analyze latest changes


Important

Adds internationalization support to the Stack Auth dashboard with English and Chinese languages, and includes Docker management scripts and development server utilities.

  • i18n Implementation:
    • Addsnext-intl for managing translations innext.config.mjs.
    • Supports English and Chinese (Simplified) languages.
    • Implements language switcher inlanguage-switcher.tsx and integrates it intonavbar.tsx andsidebar-layout.tsx.
    • Updates various components and pages to useuseTranslations for text, e.g.,project-settings/page-client.tsx,users/page-client.tsx,i18n-test/page.tsx.
  • Translation Files:
    • Addsen.json andzh.json with over 1000+ strings for translation.
  • Routing and Request Handling:
    • Configures routing inrouting.ts and request handling inrequest.ts for locale management.
  • Miscellaneous:
    • Adds Docker management scripts (docker.compose.minimal.yaml,check-docker.sh,fix-docker.sh) and development server utilities (kill-dev-servers.sh,restart-dev-basic.sh,start-dev.sh).
    • Updatespackage.json with new npm scripts for Docker and development management.

This description was created byEllipsis foraa2321b. You cancustomize this summary. It will automatically update as commits are pushed.

Summary by CodeRabbit

  • New Features

    • Internationalization added across the Dashboard with English and Chinese support.
    • Language Switcher in the navbar for quick locale changes.
    • Localized UI across pages, dialogs, navigation, data tables, tooltips, and pagination.
    • Dynamic, translation-driven navigation and breadcrumbs.
    • Added an i18n test page to preview translations.
  • Documentation

    • New guide for Docker dependency environments (full vs. minimal), usage, and FAQs.
  • Chores

    • Minimal Docker compose for dependencies.
    • New scripts to start/stop/restart dev environments, kill servers, check/fix Docker, and health-check services.

greptile-apps[bot] reacted with confused emojirecurseml[bot] reacted with heart emojiellipsis-dev[bot] reacted with rocket emoji
…ions- Integrate next-intl for internationalization support- Add Chinese (zh) and English (en) translation files- Add language switcher component in navbar- Internationalize all data tables (users, teams, transactions, api-keys, permissions, payments)- Add i18n props to DataTable components in stack-ui- Translate all page components and dialogs- Add i18n test page for development
- Add kill-dev-servers.sh to gracefully stop dev processes without affecting Docker- Add restart-dev-basic.sh for intelligent dev server restart- Add fix-docker.sh to diagnose and fix Docker daemon issues- Add check-docker.sh for Docker health verification- Update start-dev.sh with improved Docker dependency management- Add docker.compose.minimal.yaml for minimal dependency setup- Add comprehensive README.md for Docker dependencies- Add npm scripts: restart-dev-basic and fix-docker- Improve process tree killing to preserve Docker containers- Add intelligent port management to avoid Docker conflicts
@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign ourContributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let usrecheck it.

@coderabbitai
Copy link
Contributor

coderabbitaibot commentedOct 10, 2025
edited
Loading

Walkthrough

Adds full internationalization to the dashboard using next-intl: introduces locale routing/config, message bundles (en, zh), provider setup in RootLayout, a LanguageSwitcher, and widespread replacement of hard-coded strings with translations across pages, tables, dialogs, and navigation. Extends stack-ui data table components to accept i18n labels. Adds minimal Docker environment and helper scripts.

Changes

Cohort / File(s)Summary
i18n core integration
apps/dashboard/next.config.mjs,apps/dashboard/package.json,apps/dashboard/src/app/layout.tsx,apps/dashboard/src/i18n/request.ts,apps/dashboard/src/i18n/routing.ts,apps/dashboard/src/app/(main)/i18n-test/page.tsx
Integrates next-intl plugin and provider, loads locale/messages on server, defines routing/locales, and adds an i18n test page. Adds next-intl dependency.
Locale message bundles
apps/dashboard/messages/en.json,apps/dashboard/messages/zh.json
Adds comprehensive English and Chinese translations for dashboard UI, including placeholders and nested namespaces.
Navigation and language switching
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx,apps/dashboard/src/components/language-switcher.tsx,apps/dashboard/src/components/navbar.tsx
Replaces hard-coded navigation with translation-driven items, adds LanguageSwitcher (cookie + refresh), and wires it into the navbar with minor layout tweaks.
Protected pages i18n updates
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx,.../(outside-dashboard)/projects/page-client.tsx,.../projects/[projectId]/api-keys/page-client.tsx,.../domains/page-client.tsx,.../email-drafts/page-client.tsx,.../email-templates/page-client.tsx,.../email-themes/page-client.tsx,.../emails/page-client.tsx,.../payments/transactions/page-client.tsx,.../project-permissions/page-client.tsx,.../project-settings/page-client.tsx,.../team-permissions/page-client.tsx,.../team-settings/page-client.tsx,.../teams/page-client.tsx,.../users/page-client.tsx,.../users/[userId]/page-client.tsx,.../webhooks/page-client.tsx
Replaces UI literals with translation keys across titles, labels, placeholders, tooltips, dialogs, errors/success messages. No business logic changes.
Overview components i18n
.../[projectId]/(overview)/globe.tsx,.../(overview)/line-chart.tsx,.../(overview)/metrics-page.tsx,.../(overview)/setup-page.tsx
Localizes overview charts, globe labels, metrics, and setup workflow strings.
Dashboard data tables i18n
apps/dashboard/src/components/data-table/api-key-table.tsx,payment-item-table.tsx,payment-product-table.tsx,permission-table.tsx,team-member-table.tsx,team-table.tsx,transaction-table.tsx,user-table.tsx
Adds translation hooks, localized column headers, toolbars, pagination labels, actions, and dialogs. Some helpers now accept translation functions; i18n props passed to DataTable.
Form/dialog i18n
apps/dashboard/src/components/user-dialog.tsx
Localizes field labels, hints, validation messages, and dialog text via next-intl.
stack-ui data table i18n API
packages/stack-ui/src/components/data-table/data-table.tsx,pagination.tsx,toolbar.tsx,view-options.tsx
Adds DataTableI18n type and i18n props to table, toolbar, pagination, and view options. New label props and defaults; no behavior changes.
Docker minimal deps and docs
docker/dependencies/docker.compose.minimal.yaml,docker/dependencies/README.md
Adds a minimal Docker dependency stack and documentation.
Dev scripts and npm scripts
scripts/check-docker.sh,scripts/fix-docker.sh,scripts/kill-dev-servers.sh,scripts/restart-dev-basic.sh,scripts/start-dev.sh,package.json
Adds scripts to verify/fix Docker, start/stop minimal deps, restart dev basics, and new npm script entries.

Sequence Diagram(s)

sequenceDiagram  autonumber  participant Browser  participant NextApp as Next.js App (Server)  participant i18nReq as i18n/request.ts  participant IntlProv as NextIntlClientProvider  Browser->>NextApp: Request page  NextApp->>i18nReq: getLocale() / getMessages()  i18nReq-->>NextApp: { locale, messages }  NextApp->>IntlProv: Render with { locale, messages }  IntlProv-->>Browser: HTML with localized UI
Loading
sequenceDiagram  autonumber  participant User  participant LangSwitch as LanguageSwitcher (Client)  participant Browser  participant NextApp as Next.js App (Server)  participant i18nReq as i18n/request.ts  User->>LangSwitch: Select language (e.g., zh)  LangSwitch->>Browser: Set cookie NEXT_LOCALE=zh (1y)  LangSwitch->>Browser: router.refresh()  Browser->>NextApp: New request  NextApp->>i18nReq: Resolve locale (cookie → routing)  i18nReq-->>NextApp: { locale: zh, messages: zh.json }  NextApp-->>Browser: Localized response (zh)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • N2D4

Poem

A rabbit taps keys with a multilingual grin,
Flags on the breeze, translations begin.
Tables now whisper in tongues two and bright,
Switch with a click, and locales take flight.
Docker hums softly, seven gears spin—
Hop, hop, hooray! Let the global dash begin. 🐇🌍

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.94% which is insufficient. The required threshold is 80.00%.You can run@coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Title Check✅ PassedThe pull request title “dashboard i18n” succinctly identifies the main change of adding internationalization support to the dashboard and is directly related to the primary objective of the PR. It focuses on the core feature without extraneous details and is concise enough for a teammate scanning the history to understand the main update. The phrasing avoids vague terms and clearly conveys the intended scope of the changes.
Description Check✅ PassedThe pull request description begins with the required contributor guidelines comment and includes a thorough high-level summary, detailed changes, review order suggestions, and flagged inconsistencies, satisfying the repository’s minimal template requirement. It provides clear context on the internationalization implementation, Docker and development script updates, and reviewer guidance, ensuring all critical information is present for an effective review. The inclusion of CLA assistant information and nested details offers transparency and completeness. Overall, the description is well-structured and aligned with expectations.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment@coderabbitai help to get the list of available commands and usage tips.

@fuwenbinfuwenbin marked this pull request as ready for reviewOctober 10, 2025 07:56
"stop-deps:minimal":"POSTGRES_DELAY_MS=0 pnpm run deps-compose:minimal kill && POSTGRES_DELAY_MS=0 pnpm run deps-compose:minimal down -v",
"wait-until-postgres-is-ready:pg_isready":"until pg_isready -h localhost -p 5432; do sleep 1; done",
"wait-until-postgres-is-ready":"command -v pg_isready >/dev/null 2>&1 && pnpm run wait-until-postgres-is-ready:pg_isready || sleep 10 # not everyone has pg_isready installed, so we fallback to sleeping",
"start-deps:no-delay":"pnpm pre && pnpm run deps-compose up --detach --build && pnpm run wait-until-postgres-is-ready && pnpm run db:init && echo\"\\nDependencies started in the background as Docker containers. 'pnpm run stop-deps' to stop them\"n",
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo detected: The echo command in this script ends with"n which appears to be a typo. Possibly it should be\n inside the quotes, or the trailingn should be removed.

Suggested change
"start-deps:no-delay":"pnpm pre && pnpm run deps-compose up --detach --build && pnpm run wait-until-postgres-is-ready && pnpm run db:init && echo\"\\nDependencies started in the background as Docker containers. 'pnpm run stop-deps' to stop them\"n",
"start-deps:no-delay":"pnpm pre && pnpm run deps-compose up --detach --build && pnpm run wait-until-postgres-is-ready && pnpm run db:init && echo\"\\nDependencies started in the background as Docker containers. 'pnpm run stop-deps' to stop them\"",

Copy link

@recursemlrecursemlbot left a comment
edited
Loading

Choose a reason for hiding this comment

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

Review by RecurseML

🔍 Review performed on017b43f..aa2321b

✨ No bugs found, your code is sparkling clean

✅ Files analyzed, no issues (50)

apps/dashboard/messages/en.json
apps/dashboard/messages/zh.json
apps/dashboard/next.config.mjs
apps/dashboard/package.json
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/setup-page.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/api-keys/page-client.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/domains/page-client.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-drafts/page-client.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-templates/page-client.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-themes/page-client.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/emails/page-client.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/transactions/page-client.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-permissions/page-client.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/team-permissions/page-client.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/team-settings/page-client.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/teams/page-client.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/[userId]/page-client.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/webhooks/page-client.tsx
apps/dashboard/src/app/(main)/i18n-test/page.tsx
apps/dashboard/src/app/layout.tsx
apps/dashboard/src/components/data-table/api-key-table.tsx
apps/dashboard/src/components/data-table/payment-item-table.tsx
apps/dashboard/src/components/data-table/payment-product-table.tsx
apps/dashboard/src/components/data-table/permission-table.tsx
apps/dashboard/src/components/data-table/team-member-table.tsx
apps/dashboard/src/components/data-table/team-table.tsx
apps/dashboard/src/components/data-table/transaction-table.tsx
apps/dashboard/src/components/data-table/user-table.tsx
apps/dashboard/src/components/language-switcher.tsx
apps/dashboard/src/components/navbar.tsx
apps/dashboard/src/components/user-dialog.tsx
apps/dashboard/src/i18n/request.ts
apps/dashboard/src/i18n/routing.ts
docker/dependencies/README.md
docker/dependencies/docker.compose.minimal.yaml
package.json
packages/stack-ui/src/components/data-table/data-table.tsx
packages/stack-ui/src/components/data-table/pagination.tsx
packages/stack-ui/src/components/data-table/toolbar.tsx
packages/stack-ui/src/components/data-table/view-options.tsx
pnpm-lock.yaml
scripts/check-docker.sh

⏭️ Files skipped (4)
  Locations  
scripts/fix-docker.sh
scripts/kill-dev-servers.sh
scripts/restart-dev-basic.sh
scripts/start-dev.sh

Copy link

@vercelvercelbot left a comment

Choose a reason for hiding this comment

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

Additional Comments:

apps/dashboard/src/middleware.tsx (line 37):
The middleware doesn't integrate with next-intl for locale detection, causing the i18n request handler to always receive undefined for requestLocale and rely entirely on cookies.

View Details
📝 Patch Details
diff --git a/apps/dashboard/src/middleware.tsx b/apps/dashboard/src/middleware.tsxindex 8e71b71e..384b974d 100644--- a/apps/dashboard/src/middleware.tsx+++ b/apps/dashboard/src/middleware.tsx@@ -5,6 +5,8 @@ import { StackAssertionError } from '@stackframe/stack-shared/dist/utils/errors' import { wait } from '@stackframe/stack-shared/dist/utils/promises'; import type { NextRequest } from 'next/server'; import { NextResponse } from 'next/server';+import createIntlMiddleware from 'next-intl/middleware';+import { routing } from './i18n/routing';  const corsAllowedRequestHeaders = [   // General@@ -34,6 +36,8 @@ const corsAllowedResponseHeaders = [   'x-stack-known-error', ];+const intlMiddleware = createIntlMiddleware(routing);+ export async function middleware(request: NextRequest) {   const delay = Number.parseInt(getEnvVariable('STACK_ARTIFICIAL_DEVELOPMENT_DELAY_MS', '0'));   if (delay) {@@ -48,28 +52,32 @@ export async function middleware(request: NextRequest) {   const url = new URL(request.url);   const isApiRequest = url.pathname.startsWith('/api/');-  // default headers-  const responseInit: ResponseInit = {-    headers: {-      // CORS headers-      ...!isApiRequest ? {} : {+  // Skip i18n middleware for API routes+  if (isApiRequest) {+    // default headers for API routes+    const responseInit: ResponseInit = {+      headers: {+        // CORS headers         "Access-Control-Allow-Origin": "*",         "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",         "Access-Control-Allow-Headers": corsAllowedRequestHeaders.join(', '),         "Access-Control-Expose-Headers": corsAllowedResponseHeaders.join(', '),       },-    },-  };+    };++    // we want to allow preflight requests to pass through+    // even if the API route does not implement OPTIONS+    if (request.method === 'OPTIONS') {+      return new Response(null, responseInit);+    }-  // we want to allow preflight requests to pass through-  // even if the API route does not implement OPTIONS-  if (request.method === 'OPTIONS' && isApiRequest) {-    return new Response(null, responseInit);+    return NextResponse.next(responseInit);   }-  return NextResponse.next(responseInit);+  // Handle i18n for non-API routes+  return intlMiddleware(request); }  export const config = {-  matcher: '/:path*',+  matcher: ['/((?!api|_next|_vercel|.*\\..*).*)', '/api/v1/auth/signin'], };

Analysis

Missing next-intl middleware integration prevents locale detection

What fails: The middleware inapps/dashboard/src/middleware.tsx lacks next-intl integration, causingrequestLocale ingetRequestConfig() to always be undefined, forcing the system to rely solely on cookies for locale detection.

How to reproduce:

  1. Visit dashboard without existing locale cookie
  2. Check network requests - nox-next-intl-locale header is attached
  3. Server-side rendering defaults to fallback locale instead of browser preference

Result: The locale detection priority order (request → cookie → default) is broken. Browser language preferences are ignored on initial page load.

Expected: Pernext-intl middleware docs, middleware should handle locale negotiation and attach locale headers even whenlocalePrefix: 'never' is configured.

Copy link
Contributor

@coderabbitaicoderabbitaibot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (10)
apps/dashboard/src/components/data-table/payment-product-table.tsx (1)

117-126:Localize the delete dialog and toast before shipping

Delete confirmation still renders hard-coded English strings (“Delete Product”, description, “Product deleted” toast). With the new locale switcher, these won’t change when users pick Chinese, so the dialog regresses i18n coverage in this flow. Please source these strings from thetDialog (and, if needed, add a namespace entry for the toast) so the dialog and success feedback honor the active locale.

-        title="Delete Product"-        description="This action will permanently delete this product."+        title={tDialog('title')}+        description={tDialog('description')}-            toast({ title: "Product deleted" });+            toast({ title: tDialog('successToast') });
apps/dashboard/src/components/data-table/payment-item-table.tsx (1)

129-142:Replace blocking error toasts with inline alerts

These flows return"prevent-close" but surface failures only viatoast({ variant: "destructive" }), which violates the dashboard rule, “For blocking alerts and errors in UI, do not use toast notifications; use alerts instead.” Please present the error inside the open dialog (e.g., local alert state, SmartFormDialog error slot) so the message stays anchored to the blocking action. As per coding guidelines.

Also applies to: 181-192

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/[userId]/page-client.tsx (2)

445-451:Translate Yup validation error message.

The form schema contains a hardcoded English validation error message. This should use a translation key for consistency.

Apply this diff:

+  const tValidation = useTranslations('userDetail.contactChannels.sendEmailDialog.validation');   formSchema={yup.object({     selected: yup.string().defined(),-    localhostPort: yup.number().test("required-if-localhost", "Required if localhost is selected", (value, context) => {+    localhostPort: yup.number().test("required-if-localhost", tValidation('localhostPortRequired'), (value, context) => {       return context.parent.selected === "localhost" ? value !== undefined : true;     }),

836-885:Translate Yup validation error messages.

The form schema contains multiple hardcoded English validation error messages (lines 838-839, 857-858, 864). These should use translation keys for consistency with the i18n implementation.

Apply this diff:

+  const tValidation = useTranslations('userDetail.oauthProviders.dialog.validation');   const formSchema = yup.object({     providerId: yup.string()-      .defined("Provider is required")-      .nonEmpty("Provider is required")+      .defined(tValidation('providerRequired'))+      .nonEmpty(tValidation('providerRequired'))       .label(tFields('provider'))       .meta({         // ...       }),     email: yup.string()-      .email("Please enter a valid e-mail address")+      .email(tValidation('invalidEmail'))       .optional()       .label(tFields('email'))       .meta({         // ...       }),     accountId: yup.string()-      .defined("Account ID is required")+      .defined(tValidation('accountIdRequired'))       .label(tFields('accountId'))
apps/dashboard/src/components/data-table/transaction-table.tsx (1)

65-65:Untranslated column header: Product / Item.

Line 65 has a hardcoded English header while all other columns use translations. This creates an inconsistent localization experience.

Apply this diff to add translation:

   {     accessorKey: 'product_or_item',-    header: ({ column }) => <DataTableColumnHeader column={column} columnTitle="Product / Item" />,+    header: ({ column }) => <DataTableColumnHeader column={column} columnTitle={t('productOrItem')} />,     cell: ({ row }) => (

And ensure the corresponding translation key exists in the message files:

{"transactions": {"table": {"columns": {"productOrItem":"Product / Item"      }    }  }}
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-themes/page-client.tsx (1)

47-80:Incomplete i18n coverage in dialog and form elements.

The page title and description are properly localized, but several user-facing strings remain hardcoded within the dialog:

  • Line 53:"Currently using ${selectedThemeData.displayName}" (partially hardcoded)
  • Line 59:"Set Theme"
  • Line 62:"Select Email Theme"
  • Line 65:"Save Theme"
  • Lines 119-146: NewThemeButton dialog strings ("New Theme","Theme Name","Enter theme name")

To complete the i18n implementation, add these strings to theemailThemes namespace in en.json/zh.json and replace them with translation calls. For example:

       <SettingCard         title={t('activeTheme.title')}-        description={`Currently using ${selectedThemeData.displayName}`}+        description={t('activeTheme.description', { themeName: selectedThemeData.displayName })}       >

Then add to emailThemes in translation files:

{"activeTheme": {"title":"Active Theme","description":"Currently using {themeName}"  }}
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-templates/page-client.tsx (1)

83-111:Localize the NewTemplateButton component.

TheNewTemplateButton function uses hard-coded strings ("New Template", "Template Name", "Enter template name"), which is inconsistent with the i18n integration applied to the rest of the file.

Apply this diff to localize the component:

 function NewTemplateButton() {+  const t = useTranslations('emailTemplates');   const stackAdminApp = useAdminApp();   const router = useRouter();   const handleCreateNewTemplate = async (values: { name: string }) => {     const { id } = await stackAdminApp.createEmailTemplate(values.name);     router.push(`email-templates/${id}`);   };   return (     <FormDialog-      title="New Template"-      trigger={<Button>New Template</Button>}+      title={t('newTemplate.title')}+      trigger={<Button>{t('newTemplate.button')}</Button>}       onSubmit={handleCreateNewTemplate}       formSchema={yup.object({         name: yup.string().defined(),       })}       render={(form) => (         <InputField           control={form.control}           name="name"-          label="Template Name"-          placeholder="Enter template name"+          label={t('newTemplate.nameLabel')}+          placeholder={t('newTemplate.namePlaceholder')}           required         />       )}     />   ); }

Then add the corresponding keys toapps/dashboard/messages/en.json:

"emailTemplates": {..."newTemplate": {"title":"New Template","button":"New Template","nameLabel":"Template Name","namePlaceholder":"Enter template name"  }}
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/emails/page-client.tsx (2)

287-293:Bug: config shape inconsistency (likely runtime error)

You read project.config.emailConfig, but elsewhere use project.useConfig().emails.server. This mismatch will break checks and sendTestEmail config.

 function TestSendingDialog(props: {   trigger: React.ReactNode, }) {   const stackAdminApp = useAdminApp();   const project = stackAdminApp.useProject();   const { toast } = useToast();   const [error, setError] = useState<string | null>(null);+  const config = project.useConfig();...   onSubmit={async (values) => {-      const emailConfig = project.config.emailConfig || throwErr("Email config is not set");-      if (emailConfig.type === 'shared') throwErr("Shared email server cannot be used for testing");+      const emailConfig = config.emails.server || throwErr("Email config is not set");+      if (emailConfig.isShared) throwErr("Shared email server cannot be used for testing");        const result = await stackAdminApp.sendTestEmail({         recipientEmail: values.email,-        emailConfig: emailConfig,+        emailConfig,       });

427-434:Blocking error uses toast; use inline Alert instead

This prevents close and informs the user; per guidelines, avoid toast for blocking errors. Show an inline Alert in the dialog.

As per coding guidelines

-    if (selectedUsers.length === 0) {-      toast({-        title: "No recipients selected",-        description: "Please select at least one recipient to send the email.",-        variant: "destructive",-      });-      return "prevent-close" as const;-    }+    if (selectedUsers.length === 0) {+      setRecipientsError(tSend('selectRecipientsError')); // e.g., "Please select at least one recipient."+      return "prevent-close" as const;+    }

And add local state and inline Alert:

 function SendEmailDialog(props: {   trigger: React.ReactNode,   emailConfig: CompleteConfig['emails']['server'], }) {   const stackAdminApp = useAdminApp();   const { toast } = useToast();   const [open, setOpen] = useState(false);   const [sharedSmtpDialogOpen, setSharedSmtpDialogOpen] = useState(false);   const [selectedUsers, setSelectedUsers] = useState<ServerUser[]>([]);   const [stage, setStage] = useState<'recipients' | 'data'>('recipients');+  const [recipientsError, setRecipientsError] = useState<string | null>(null);+  const tSend = useTranslations('emails.sendDialog');...             {stage === 'recipients' ? (               <TeamMemberSearchTable                 action={(user) => (                   <Button                     size="sm"                     variant="outline"                     onClick={() => setSelectedUsers(users =>                       users.some(u => u.id === user.id)                         ? users.filter(u => u.id !== user.id)                         : [...users, user]                     )}                   >                     {selectedUsers.some(u => u.id === user.id) ? 'Remove' : 'Add'}                   </Button>                 )}               />             ) : (

And render an Alert when error:

-            {stage === 'recipients' ? (+            {stage === 'recipients' ? (+              <>+                {recipientsError && <Alert variant="destructive">{recipientsError}</Alert>}               <TeamMemberSearchTable
apps/dashboard/src/components/data-table/team-member-table.tsx (1)

75-94:Localize Cancel label in EditPermissionDialog

Ensure Cancel is translated.

   return <SmartFormDialog     open={props.open}     onOpenChange={props.onOpenChange}     title={t('title')}     formSchema={formSchema}     okButton={{ label: t('save') }}@@-    cancelButton+    cancelButton={{ label: t('cancel') }}   />;
🧹 Nitpick comments (28)
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx (1)

29-36:Memoize the schema to avoid recreation on every render.

TheprojectFormSchema is recreated on every component render, which is inefficient. Since the schema depends on the translation functiont, wrap it inuseMemo witht as a dependency.

Apply this diff:

+import { useMemo } from "react"; export default function PageClient() {   const t = useTranslations('newProject');   const user = useUser({ or: 'redirect', projectIdMustMatch: "internal" });   const [loading, setLoading] = useState(false);   const router = useRouter();   const searchParams = useSearchParams();   const displayName = searchParams.get("display_name");-  const projectFormSchema = yup.object({-    displayName: yup.string().min(1, t('validation.displayNameRequired')).defined().nonEmpty(t('validation.displayNameRequired')),-    signInMethods: yup.array(yup.string().oneOf(["credential", "magicLink", "passkey"].concat(allProviders)).defined())-      .min(1, t('validation.signInMethodRequired'))-      .defined(t('validation.signInMethodRequired')),-    teamId: yup.string().uuid().defined(t('validation.teamRequired')),-  });+  const projectFormSchema = useMemo(() => yup.object({+    displayName: yup.string().min(1, t('validation.displayNameRequired')).defined().nonEmpty(t('validation.displayNameRequired')),+    signInMethods: yup.array(yup.string().oneOf(["credential", "magicLink", "passkey"].concat(allProviders)).defined())+      .min(1, t('validation.signInMethodRequired'))+      .defined(t('validation.signInMethodRequired')),+    teamId: yup.string().uuid().defined(t('validation.teamRequired')),+  }), [t]);
apps/dashboard/src/components/user-dialog.tsx (1)

67-73:Consider using tErrors namespace for validation messages.

Line 69 usestHints('emailVerifiedRequired') for a Yup validation error message, while the same key is reused as a UI hint on line 151. Validation error messages typically belong in the error namespace for consistency and clarity. Consider either:

  • UsingtErrors('emailVerifiedRequired') here and keepingtHints('emailVerifiedRequired') for the UI hint (separate keys with the same text in translation files), or
  • Moving this totErrors and referencing that namespace on line 151 if the same message is appropriate in both contexts.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx (2)

254-257:Avoid shadowing the translation functiont.

The callback parameter inteams.find(t => ...) on line 256 shadows the translation functiont. While this works due to scoping, it reduces code clarity.

Apply this diff to use a clearer parameter name:

 {t('transfer.confirm', {   projectName: project.displayName,-  teamName: teams.find(t => t.id === selectedTeamId)?.displayName+  teamName: teams.find(team => team.id === selectedTeamId)?.displayName })}

304-304:Consider consolidating sentence fragments for better translation flexibility.

Splitting a sentence across multiple translation keys (line 304) can cause issues in languages with different word orders. The bold text might need to appear in a different position in some languages.

Consider using a single translation key with ICU rich text formatting (next-intl supports this in v4+):

// In your translation file:{"dangerZone":{"irreversibleWarning":"This action is <bold>irreversible</bold>. This will permanently delete:"}}// In the component:{t.rich('dangerZone.irreversibleWarning',{bold:(chunks)=><strong>{chunks}</strong>})}

Alternatively, if keeping the current approach, ensure translators understand the context and sentence structure in all languages.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsx (1)

29-29:Consider using a single translation key with rich text formatting.

The welcome message currently uses multiple separate translation calls concatenated together. This approach can make translation difficult as translators may not have full context.

Consider refactoring to use a single translation key with ICU message format for rich text:

{t.rich('welcomeMessage',{link:(chunks)=><StyledLinkhref="https://docs.stack-auth.com">{chunks}</StyledLink>})}

And update the translation file to:

{"welcomeMessage":"Welcome to Stack Auth! Check out our <link>documentation</link> to get started."}

This approach provides translators with full context and allows them to reorder elements as needed for different languages.

docker/dependencies/docker.compose.minimal.yaml (1)

108-108:Minor formatting: Remove extra blank line.

YAMLlint reports too many blank lines at the end of the file.

 volumes:   postgres-data:   inbucket-data:   svix-redis-data:   svix-postgres-data:   s3mock-data:   deno-cache:-
apps/dashboard/messages/zh.json (1)

1-1053:LGTM! Translation file structure is well-organized.

The Chinese translation file is comprehensive and properly structured to match the dashboard's i18n namespace hierarchy. A spot check reveals consistent placeholder formatting (e.g.,{userName},{teamName},{page}) and no obvious HTML injection risks.

However, consider these optional improvements:

  1. Translation quality verification: Have a native Chinese speaker review technical terminology and UI strings for naturalness and accuracy.
  2. Placeholder validation: Consider adding a build-time check to ensure all placeholders in translations match those in the English source (en.json).
apps/dashboard/src/components/language-switcher.tsx (2)

27-27:Consider smoother locale transition without full page refresh.

Callingrouter.refresh() triggers a full page reload, which may cause a jarring user experience. Next-intl and Next.js App Router support smoother locale transitions.

Consider using Next.js'suseRouter fromnext/navigation with a locale-aware approach, or investigate if next-intl provides a smoother transition mechanism that updates locale context without a hard refresh. The current implementation works but could be improved for better UX.

If a refresh is necessary due to server-side locale detection, document this decision in a comment explaining why a full refresh is required.


13-16:Hardcoded language list limits extensibility.

The languages array is hardcoded in the component. If more languages are added to the routing configuration (apps/dashboard/src/i18n/routing.ts), this list must be manually updated in sync.

Consider deriving the available languages from the routing configuration or a shared constants file:

// src/i18n/languages.tsexportconstlanguages=[{code:'en',name:'English',flag:'🇺🇸'},{code:'zh',name:'中文',flag:'🇨🇳'},]asconst;// Validate against routing config at build timeimport{routing}from'./routing';constlocaleSet=newSet(routing.locales);languages.forEach(lang=>{if(!localeSet.has(lang.code)){thrownewError(`Language${lang.code} not in routing config`);}});

Then import and use this shared constant in both the language switcher and any other components that need the language list.

apps/dashboard/src/i18n/request.ts (2)

16-16:Type assertions bypass type safety for locale validation.

The code usesas any type assertions when checking if the locale is included inrouting.locales. This bypasses TypeScript's type checking and could mask type mismatches.

Consider adding proper typing or using a type guard:

// In routing.ts, export locale typeexporttypeLocale=typeofrouting.locales[number];// In request.tsimport{routing,typeLocale}from'./routing';functionisValidLocale(locale:string):locale isLocale{returnrouting.locales.includes(localeasLocale);}// Then use:if(localeCookie&&isValidLocale(localeCookie.value)){locale=localeCookie.value;}if(!locale||!isValidLocale(locale)){locale=routing.defaultLocale;}

This provides better type safety and makes the intent clearer.

Also applies to: 22-22


26-29:Add error handling for missing locale message files.

The dynamic import at line 28 will throw an unhandled error if a locale's message file is missing or malformed. While this should not occur in production, it could cause issues during development or if the build process has problems.

Add error handling and a fallback:

try{return{      locale,messages:(awaitimport(`../../messages/${locale}.json`)).default};}catch(error){console.error(`Failed to load messages for locale "${locale}":`,error);// Fallback to default locale messagesreturn{locale:routing.defaultLocale,messages:(awaitimport(`../../messages/${routing.defaultLocale}.json`)).default};}

This prevents the entire app from crashing if a locale file is missing and provides a graceful degradation path.

scripts/fix-docker.sh (1)

1-105:Consider using English for script messages.

This script is part of an i18n PR focused on dashboard UI localization, yet the script itself uses Chinese-only messages. For developer tooling and scripts, English is the conventional choice to ensure wider accessibility and maintainability across international teams.

Apply this diff to replace Chinese messages with English:

-echo "🔧 Docker Desktop 修复工具"+echo "🔧 Docker Desktop Repair Tool" echo "================================" echo ""-# Step 1: 检查 Docker Desktop 状态-echo "📋 Step 1: 检查当前状态..."+# Step 1: Check Docker Desktop status+echo "📋 Step 1: Checking current status..." if pgrep -q "Docker Desktop"; then-    echo "   ✅ Docker Desktop UI 正在运行"+    echo "   ✅ Docker Desktop UI is running" else-    echo "   ❌ Docker Desktop UI 未运行"+    echo "   ❌ Docker Desktop UI is not running" fi if pgrep -q "com.docker.backend"; then-    echo "   ✅ Docker Backend 正在运行"+    echo "   ✅ Docker Backend is running"     echo ""-    echo "Docker 看起来正常,尝试连接..."-    docker ps > /dev/null 2>&1 && echo "   ✅ Docker 工作正常!" && exit 0+    echo "Docker appears healthy, attempting connection..."+    docker ps > /dev/null 2>&1 && echo "   ✅ Docker is working!" && exit 0 else-    echo "   ❌ Docker Backend 未运行(这是问题所在!)"+    echo "   ❌ Docker Backend is not running (this is the problem!)" fi echo ""-# Step 2: 完全停止 Docker-echo "📋 Step 2: 完全停止 Docker Desktop..."+# Step 2: Stop Docker completely+echo "📋 Step 2: Stopping Docker Desktop completely..." osascript -e 'quit app "Docker"' 2>/dev/null || true sleep 3-# 强制杀死所有 Docker 进程-echo "   → 清理残留进程..."+# Force kill all Docker processes+echo "   → Cleaning up remaining processes..." pkill -9 -f "Docker Desktop" 2>/dev/null || true pkill -9 -f "com.docker" 2>/dev/null || true sleep 2-# Step 3: 清理可能的锁文件-echo "📋 Step 3: 清理锁文件和状态..."+# Step 3: Clean potential lock files+echo "📋 Step 3: Cleaning lock files and state..." rm -f ~/Library/Group\ Containers/group.com.docker/docker.sock.lock 2>/dev/null || true rm -f ~/.docker/run/*.lock 2>/dev/null || true-echo "   ✅ 清理完成"+echo "   ✅ Cleanup complete" echo ""-# Step 4: 重启 Docker Desktop-echo "📋 Step 4: 重新启动 Docker Desktop..."+# Step 4: Restart Docker Desktop+echo "📋 Step 4: Restarting Docker Desktop..." open -a Docker-echo "   等待 Docker Desktop 启动..."+echo "   Waiting for Docker Desktop to start..." sleep 10-# 等待最多 60 秒让 Docker 完全启动-echo "   检查 Docker daemon 是否准备就绪..."+# Wait up to 60 seconds for Docker to fully start+echo "   Checking if Docker daemon is ready..." COUNTER=0 MAX_WAIT=60 while [ $COUNTER -lt $MAX_WAIT ]; do     if docker info > /dev/null 2>&1; then-        echo "   ✅ Docker daemon 已就绪!"+        echo "   ✅ Docker daemon is ready!"         break     fi          if pgrep -q "com.docker.backend"; then-        echo "   ⏳ Backend 进程已启动,等待就绪... ($COUNTER/$MAX_WAIT)"+        echo "   ⏳ Backend process started, waiting for ready... ($COUNTER/$MAX_WAIT)"     else-        echo "   ⏳ 等待 Backend 进程启动... ($COUNTER/$MAX_WAIT)"+        echo "   ⏳ Waiting for Backend process to start... ($COUNTER/$MAX_WAIT)"     fi          sleep 2     COUNTER=$((COUNTER + 2)) done echo ""-# Step 5: 验证-echo "📋 Step 5: 验证 Docker 状态..."+# Step 5: Verify+echo "📋 Step 5: Verifying Docker status..." if docker info > /dev/null 2>&1; then-    echo "   ✅ Docker 工作正常!"+    echo "   ✅ Docker is working!"     echo ""     docker version     echo ""     echo "================================"-    echo "✨ 修复成功!现在可以启动开发环境了:"+    echo "✨ Repair successful! You can now start the dev environment:"     echo "   pnpm run start-deps:minimal"     echo "================================"     exit 0 else-    echo "   ❌ Docker 仍然无法连接"+    echo "   ❌ Docker still cannot connect"     echo ""     echo "================================"-    echo "⚠️  自动修复失败,请尝试:"+    echo "⚠️  Auto-repair failed, please try:"     echo ""-    echo "1. 手动重启 Docker Desktop:"-    echo "   - 点击菜单栏的 Docker 图标"-    echo "   - 选择 'Restart'"+    echo "1. Manually restart Docker Desktop:"+    echo "   - Click the Docker icon in the menu bar"+    echo "   - Select 'Restart'"     echo ""-    echo "2. 如果还不行,完全卸载重装:"-    echo "   - 在 Docker Desktop 中选择 'Troubleshoot' > 'Clean / Purge data'"-    echo "   - 或者完全卸载后重新安装"+    echo "2. If that doesn't work, fully uninstall and reinstall:"+    echo "   - In Docker Desktop select 'Troubleshoot' > 'Clean / Purge data'"+    echo "   - Or completely uninstall and reinstall"     echo ""-    echo "3. 检查系统日志查看错误:"+    echo "3. Check system logs for errors:"     echo "   tail -f ~/Library/Containers/com.docker.docker/Data/log/vm/dockerd.log"     echo "================================"     exit 1 fi
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx (1)

50-50:Simplify the namespace override for consistency.

The error message usest('globe.errorMessage', { ns: 'overview' }) with a namespace override. Since you're already in the'overview.metrics' namespace and there's aglobe.errorMessage key atoverview.metrics.globe.errorMessage in en.json, you can simplify this tot('globe.errorMessage') without the namespace override for better clarity and consistency.

Apply this diff:

-<ErrorBoundary fallback={<div className='text-center text-sm text-red-500'>{t('globe.errorMessage', { ns: 'overview' })}</div>}>+<ErrorBoundary fallback={<div className='text-center text-sm text-red-500'>{t('globe.errorMessage')}</div>}>
scripts/restart-dev-basic.sh (1)

24-34:Add a comment explaining the container count requirement.

The script checks for at least 7 running containers, but this number might be unclear to future maintainers. Adding a brief comment would improve clarity.

Apply this diff:

+# Minimal stack requires 7 containers: postgres, inbucket, svix, redis, etc. RUNNING_CONTAINERS=$(docker ps --filter "name=stack-dependencies" --format '{{.Names}}' | wc -l) if [ "$RUNNING_CONTAINERS" -lt 7 ]; then
packages/stack-ui/src/components/data-table/pagination.tsx (1)

13-17:Solid i18n extension with safe defaults; consider minor a11y tweak

Props and defaults look good. Optional: expose previous/next labels via aria-label on the Buttons (in addition to sr-only) for clearer accessible names.

-            <Button+            <Button               variant="outline"               className="h-8 w-8 p-0"               onClick={() => table.previousPage()}               disabled={!table.getCanPreviousPage()}+              aria-label={previousPageLabel}             >               <span className="sr-only">{previousPageLabel}</span>               <ChevronLeftIcon className="h-4 w-4" />             </Button>...-            <Button+            <Button               variant="outline"               className="h-8 w-8 p-0"               onClick={() => table.nextPage()}               disabled={!table.getCanNextPage()}+              aria-label={nextPageLabel}             >               <span className="sr-only">{nextPageLabel}</span>               <ChevronRightIcon className="h-4 w-4" />             </Button>

Also applies to: 21-25

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/emails/page-client.tsx (2)

321-341:Use next-intl formatter for locale-aware date/time

Leverage useFormatter for consistent i18n formatting.
Based on learnings

-import { useTranslations } from 'next-intl';+import { useTranslations, useFormatter } from 'next-intl';... const useEmailTableColumns = (): ColumnDef<AdminSentEmail>[] => {   const t = useTranslations('emails.emailLog.columns');+  const format = useFormatter();   return [     { accessorKey: 'recipient', header: t('recipient') },     { accessorKey: 'subject', header: t('subject') },     {-      accessorKey: 'sentAt', header: t('sentAt'), cell: ({ row }) => {-        const date = row.original.sentAt;-        return date.toLocaleDateString() + ' ' + date.toLocaleTimeString();-      }+      accessorKey: 'sentAt',+      header: t('sentAt'),+      cell: ({ row }) => format.dateTime(row.original.sentAt, { dateStyle: 'medium', timeStyle: 'short' })     },

Also applies to: 327-331


169-175:i18n gaps: several hard-coded strings remain

Multiple labels, titles, button texts, and descriptions are still hard-coded; convert to useTranslations for full i18n coverage.

Examples:

  • Dialog titles/buttons: "Edit Email Server", "Save", "Send a Test Email", "Send", "Send Email", "Cancel", "Back", "Next".
  • Field labels/options: "Email server", "Shared (noreply@stackframe.co)", "Resend ...", "Custom SMTP server ...", "Resend API Key", "Sender Email/Name", "Host/Port/Username/Password", "Notification Category", "Transactional/Marketing", "Email Content", "Subject".
  • UI text: "Recipients", "Shared Email Server", "Warning", and the shared-server alert description.
  • TeamMemberSearchTable action buttons: "Add"/"Remove".

If helpful, I can generate a targeted patch applying t('...') across these spots.

Also applies to: 221-229, 231-244, 247-264, 281-286, 314-314, 450-453, 498-510, 512-522, 551-552, 557-567, 569-574

apps/dashboard/src/components/data-table/team-table.tsx (1)

112-132:Type t for safer translations

Avoid any; type t to ReturnType (or a narrowed namespace type).

-const getColumns = (t: any): ColumnDef<ServerTeam>[] =>  [+const getColumns = (t: ReturnType<typeof useTranslations>): ColumnDef<ServerTeam>[] => [
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/setup-page.tsx (1)

535-537:Localize Suspense fallback

"LOADING" is hard-coded; use a translated string.

-function GlobeIllustration() {+function GlobeIllustration() {+  const t = useTranslations('overview.setup');   return (     <div className="w-[200px] h-[200px] relative hidden md:block">-      <Suspense fallback={"LOADING"}>+      <Suspense fallback={t('loadingGlobe')}>         <GlobeIllustrationInner />       </Suspense>     </div>   ); }
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (1)

585-587:Optional: i18n for "Sidebar Menu"

Translate SheetTitle to align with the rest of the navigation texts.

-  <SheetTitle className="hidden">-    Sidebar Menu-  </SheetTitle>+  <SheetTitle className="hidden">+    {t('sidebarMenu')}+  </SheetTitle>
scripts/kill-dev-servers.sh (4)

1-4:Enable strict mode and pipefail for safer execution

Prevents silent failures and masked pipeline errors; add ERR traps if desired.

 #!/bin/bash++# Fail fast and catch pipeline errors+set -Eeuo pipefail+# Optional: basic trap+trap 'echo "kill-dev-servers aborted"; exit 1' ERR

12-29:Fix SC2155 and quote vars in kill_process_tree

Avoids masking return values and handles PIDs with defensive quoting. Based on static analysis hints.

 kill_process_tree() {-    local pid=$1-    local sig=${2:-TERM}+    local pid+    pid="$1"+    local sig+    sig="${2:-TERM}"@@-    local children=$(pgrep -P $pid 2>/dev/null)+    local children+    children=$(pgrep -P "$pid" 2>/dev/null || true)@@-        kill_process_tree $child $sig+        kill_process_tree "$child" "$sig"@@-    if kill -0 $pid 2>/dev/null; then-        kill -$sig $pid 2>/dev/null || true+    if kill -0 "$pid" 2>/dev/null; then+        kill -"${sig}" "$pid" 2>/dev/null || true     fi }

68-89:Use graceful tree kill before -9 on port PIDs

Blind kill -9 risks leaving children/orphans. Reuse kill_process_tree with TERM then KILL.

 for port in "${DEV_PORTS[@]}"; do   pids=$(lsof -ti:$port 2>/dev/null)   if [ ! -z "$pids" ]; then     echo "    ✓ Killing processes on port $port"-    echo "$pids" | xargs kill -9 2>/dev/null || true+    for pid in $pids; do+      kill_process_tree "$pid" TERM+      sleep 0.3+      # Ensure termination if still alive+      kill_process_tree "$pid" KILL+    done   fi done@@   for port in "${DOCKER_PORTS[@]}"; do     pids=$(lsof -ti:$port 2>/dev/null)     if [ ! -z "$pids" ]; then       echo "    ✓ Killing processes on port $port"-      echo "$pids" | xargs kill -9 2>/dev/null || true+      for pid in $pids; do+        kill_process_tree "$pid" TERM+        sleep 0.3+        kill_process_tree "$pid" KILL+      done     fi   done

91-93:Remove unused ALL_PORTS variable

SC2034: variable is never used.

-# Combine all ports for verification-ALL_PORTS=("${DEV_PORTS[@]}" "${DOCKER_PORTS[@]}")
scripts/start-dev.sh (2)

1-4:Harden script with pipefail and traps

Prevents masked failures (esp. pipelines) and improves resiliency.

 #!/bin/bash-set -e  # Exit on any error+set -Eeuo pipefail  # Exit on errors, unset vars, and pipeline failures+# Optional: trap failures for cleanup/log+trap 'echo "start-dev aborted"; exit 1' ERR

46-47:Pipeline masks errors from kill script

With tail, failures in kill-dev-servers.sh won’t fail this step unless pipefail is set. Either set pipefail (above) or avoid piping.

-bash "$(dirname "$0")/kill-dev-servers.sh" | tail -n 5+if ! bash "$(dirname "$0")/kill-dev-servers.sh"; then+  echo "❌ Failed to clean up dev servers"; exit 1+fi
apps/dashboard/src/components/data-table/api-key-table.tsx (1)

12-22:Consider i18n for DataTableFacetedFilter internal strings

Faceted filter currently renders “No results found.” and “Clear filters” in English (see packages/stack-ui/.../faceted-filter.tsx). Add i18n props there and pass translations here.

packages/stack-ui/src/components/data-table/data-table.tsx (1)

46-135:Consider adding i18n support for the "No results." string.

The i18n prop is correctly propagated toDataTableToolbar andDataTablePagination. However, the hardcoded "No results." string at line 119 is not exposed through the i18n interface.

To fully internationalize the component, add anoResults field toDataTableI18n:

 export type DataTableI18n = {   resetFilters?: string,   exportCSV?: string,   noDataToExport?: string,   view?: string,   toggleColumns?: string,   rowsSelected?: (selected: number, total: number) => string,   rowsPerPage?: string,   previousPage?: string,   nextPage?: string,+  noResults?: string, };

Then update line 119:

-                  No results.+                  {props.i18n?.noResults || "No results."}

"next-intl":"^4.3.11",
"next-themes":"^0.2.1",
"posthog-js":"^1.235.0",
"react":"19.0.0",
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion |🟠 Major

🧩 Analysis chain

React version mismatch between dependencies and type overrides.

Line 53 specifiesreact: 19.0.0, while the pnpm overrides at line 86 specify@types/react: 19.0.12. This inconsistency may lead to type mismatches if React 19.0.0 has a different API surface than the types expect.

Run the following script to verify whether React 19.0.0 and 19.0.12 exist and check for breaking changes:

Also applies to: 86-86


🏁 Script executed:

#!/bin/bash# Check for React 19.0.0 and 19.0.12 releases and their differencesecho"=== Checking React releases ==="npm view react@19.0.0 version2>/dev/null||echo"react@19.0.0 not found"npm view react@19.0.12 version2>/dev/null||echo"react@19.0.12 not found"echo""echo"=== Checking @types/react releases ==="npm view @types/react@19.0.12 version2>/dev/null||echo"@types/react@19.0.12 not found"

Length of output: 110


Align React and @types/react versions.
react@19.0.0 exists but react@19.0.12 does not; update your override so both runtime and types match (e.g. pin @types/react to 19.0.0 or use ^19.0.0) to avoid type mismatches.

🤖 Prompt for AI Agents
In apps/dashboard/package.json around line 53, the project pins "react" to19.0.0 while the @types/react override/resolution is mismatched (refers to anon-existent 19.0.12); update the package.json overrides/resolutions so@types/react matches the runtime version (e.g., pin @types/react to 19.0.0 oruse a compatible range like ^19.0.0), then run install to refresh lockfile soruntime and types are aligned.

}catch(error){
console.error('Failed to transfer project:',error);
alert(`Failed totransfer project:${errorinstanceofError ?error.message :'Unknown error'}`);
alert(t('transfer.error',{message:errorinstanceofError ?error.message :'Unknown error'}));
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue |🟠 Major

Replace browseralert() with a proper UI component.

Using the browser's nativealert() provides a poor user experience (blocks the entire page, cannot be styled, inconsistent across browsers).

Consider using theAlert component from stack-ui or displaying the error in the transfer dialog itself:

-      alert(t('transfer.error', { message: error instanceof Error ? error.message : 'Unknown error' }));

Then update the transfer section to display the error state. For example, add error state handling:

const[transferError,setTransferError]=useState<string|null>(null);// In the catch block:setTransferError(errorinstanceofError ?error.message :'Unknown error');// In the JSX, add before the transfer controls:{transferError&&(<Alertvariant="destructive">{t('transfer.error',{message:transferError})}</Alert>)}
🤖 Prompt for AI Agents
Inapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsxaround line 65, replace the browser alert(...) call with state-driven UI errorhandling: add a transferError state (string | null), set transferError in thecatch block to error.message or 'Unknown error' instead of calling alert, importand render the Alert component (e.g., Alert variant="destructive") above thetransfer controls to show t('transfer.error', { message: transferError }) whentransferError is non-null, and ensure to clear transferError when the dialogcloses or on retry; also add necessary imports for useState and Alert.

Comment on lines +287 to +299
name:(pathname:string)=>{
constmatch=pathname.match(/^\/projects\/[^\/]+\/webhooks\/([^\/]+)$/);
lethref;
if(match){
href=`/teams/${match[1]}`;
}else{
href="";
}

return[
{item:t('webhooks'),href:"/webhooks"},
{item:t('endpoint'), href},
];
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue |🔴 Critical

Wrong breadcrumb link for webhook endpoint

Link points to /teams/{id}; should be /webhooks/{id}.

-        const match = pathname.match(/^\/projects\/[^\/]+\/webhooks\/([^\/]+)$/);+        const match = pathname.match(/^\/projects\/[^\/]+\/webhooks\/([^\/]+)$/);         let href;         if (match) {-          href = `/teams/${match[1]}`;+          href = `/webhooks/${match[1]}`;         } else {           href = "";         }          return [           { item: t('webhooks'), href: "/webhooks" },           { item: t('endpoint'), href },         ];
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
name:(pathname:string)=>{
constmatch=pathname.match(/^\/projects\/[^\/]+\/webhooks\/([^\/]+)$/);
lethref;
if(match){
href=`/teams/${match[1]}`;
}else{
href="";
}
return[
{item:t('webhooks'),href:"/webhooks"},
{item:t('endpoint'), href},
];
name:(pathname:string)=>{
constmatch=pathname.match(/^\/projects\/[^\/]+\/webhooks\/([^\/]+)$/);
lethref;
if(match){
href=`/webhooks/${match[1]}`;
}else{
href="";
}
return[
{item:t('webhooks'),href:"/webhooks"},
{item:t('endpoint'), href},
];
🤖 Prompt for AI Agents
Inapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsxaround lines 287 to 299, the breadcrumb link resolver extracts an ID from thepath but builds the href as `/teams/{id}`; change this to `/webhooks/{id}` sothe endpoint breadcrumb points to the correct resource. Update the branch thatsets href when match exists to construct `/webhooks/${match[1]}`, keep theempty-string fallback, and return the breadcrumb array unchanged otherwise.

Comment on lines 296 to 322
constformSchema=yup.object({
email:yup.string()
.email("Please enter a valid e-mail address")
.defined("E-mail is required")
.label("E-mail")
.label(t('fields.email'))
.meta({
stackFormFieldPlaceholder:"Enter e-mail address",
stackFormFieldPlaceholder:t('fields.emailPlaceholder'),
}),
isVerified:yup.boolean()
.default(false)
.label("Set as verified")
.label(t('fields.isVerified'))
.meta({
description:"E-mails verified by verification emails. Can be used for OTP/magic links"
description:t('hints.isVerified')
}),
isPrimary:yup.boolean()
.default(false)
.label("Set as primary")
.label(t('fields.isPrimary'))
.meta({
description:"Make this the primary e-mail for the user"
description:t('hints.isPrimary')
}),
isUsedForAuth:yup.boolean()
.default(false)
.label("Used for sign-in")
.label(t('fields.isUsedForAuth'))
.meta({
description:"Allow this e-mail to be used for password sign-in. Also enables OTP/magic links if the e-mail is verified."
description:t('hints.isUsedForAuth')
}),
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion |🟠 Major

Translate Yup validation error messages.

The form schema contains hardcoded English validation error messages (.email("Please enter a valid e-mail address") and.defined("E-mail is required")). These should use translation keys for consistency with the i18n implementation.

Based on the pattern shown inapps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx, validation messages should be translated. Apply this diff:

+  const tValidation = useTranslations('userDetail.contactChannels.addDialog.validation');   const formSchema = yup.object({     email: yup.string()-      .email("Please enter a valid e-mail address")-      .defined("E-mail is required")+      .email(tValidation('invalidEmail'))+      .defined(tValidation('emailRequired'))       .label(t('fields.email'))

You'll need to add the corresponding keys to your translation files:

{"userDetail": {"contactChannels": {"addDialog": {"validation": {"invalidEmail":"Please enter a valid e-mail address","emailRequired":"E-mail is required"        }      }    }  }}
🤖 Prompt for AI Agents
Inapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/[userId]/page-client.tsxaround lines 296 to 322, the Yup validators use hardcoded English messages;replace .email("Please enter a valid e-mail address") with.email(t('userDetail.contactChannels.addDialog.validation.invalidEmail')) and.defined("E-mail is required") with.defined(t('userDetail.contactChannels.addDialog.validation.emailRequired'));keep the rest of the schema intact and add the two keys to your translation JSONfiles as shown in the review comment.

Comment on lines +1 to +60
import{Card,Typography}from'@stackframe/stack-ui';
import{useTranslations}from'next-intl';

exportconstmetadata={
title:"i18n Test",
};

exportdefaultfunctionI18nTestPage(){
constt=useTranslations('common');
consttProjects=useTranslations('projects');
consttAuth=useTranslations('auth');

return(
<divclassName="container mx-auto p-8 space-y-6">
<Typographytype="h1">i18n Test Page</Typography>

<CardclassName="p-6">
<Typographytype="h2"className="mb-4">Common Translations</Typography>
<ulclassName="space-y-2">
<li>Dashboard:{t('dashboard')}</li>
<li>Projects:{t('projects')}</li>
<li>Settings:{t('settings')}</li>
<li>Save:{t('save')}</li>
<li>Cancel:{t('cancel')}</li>
<li>Loading:{t('loading')}</li>
</ul>
</Card>

<CardclassName="p-6">
<Typographytype="h2"className="mb-4">Projects Translations</Typography>
<ulclassName="space-y-2">
<li>Title:{tProjects('title')}</li>
<li>New Project:{tProjects('newProject')}</li>
<li>Create Project:{tProjects('createProject')}</li>
</ul>
</Card>

<CardclassName="p-6">
<Typographytype="h2"className="mb-4">Auth Translations</Typography>
<ulclassName="space-y-2">
<li>Sign In:{tAuth('signIn')}</li>
<li>Sign Up:{tAuth('signUp')}</li>
<li>Email:{tAuth('email')}</li>
<li>Password:{tAuth('password')}</li>
</ul>
</Card>

<CardclassName="p-6 bg-blue-50 dark:bg-blue-950">
<Typographytype="h3"className="mb-2">How to Test</Typography>
<olclassName="list-decimal list-inside space-y-1 text-sm">
<li>Check the language switcher in the navbar (top right, globe icon 🌐)</li>
<li>Click to switch between English and 中文</li>
<li>Observe how the text on this page changes</li>
<li>Try switching languages: /i18n-test (English) vs /zh/i18n-test (Chinese)</li>
</ol>
</Card>
</div>
);
}

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue |🔴 Critical

Add "use client" directive to enable hooks.

This component usesuseTranslations hooks (lines 9-11) but lacks the"use client" directive. In Next.js App Router, components are Server Components by default, and hooks can only be used in Client Components. Without this directive, the component will fail at runtime.

Apply this diff:

+"use client";+ import { Card, Typography } from '@stackframe/stack-ui'; import { useTranslations } from 'next-intl';

Alternatively, if you want to keep this as a Server Component, replaceuseTranslations with the asyncgetTranslations function:

 import { Card, Typography } from '@stackframe/stack-ui';-import { useTranslations } from 'next-intl';+import { getTranslations } from 'next-intl/server'; export const metadata = {   title: "i18n Test", };-export default function I18nTestPage() {-  const t = useTranslations('common');-  const tProjects = useTranslations('projects');-  const tAuth = useTranslations('auth');+export default async function I18nTestPage() {+  const t = await getTranslations('common');+  const tProjects = await getTranslations('projects');+  const tAuth = await getTranslations('auth');
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import{Card,Typography}from'@stackframe/stack-ui';
import{useTranslations}from'next-intl';
exportconstmetadata={
title:"i18n Test",
};
exportdefaultfunctionI18nTestPage(){
constt=useTranslations('common');
consttProjects=useTranslations('projects');
consttAuth=useTranslations('auth');
return(
<divclassName="container mx-auto p-8 space-y-6">
<Typographytype="h1">i18n Test Page</Typography>
<CardclassName="p-6">
<Typographytype="h2"className="mb-4">Common Translations</Typography>
<ulclassName="space-y-2">
<li>Dashboard:{t('dashboard')}</li>
<li>Projects:{t('projects')}</li>
<li>Settings:{t('settings')}</li>
<li>Save:{t('save')}</li>
<li>Cancel:{t('cancel')}</li>
<li>Loading:{t('loading')}</li>
</ul>
</Card>
<CardclassName="p-6">
<Typographytype="h2"className="mb-4">Projects Translations</Typography>
<ulclassName="space-y-2">
<li>Title:{tProjects('title')}</li>
<li>New Project:{tProjects('newProject')}</li>
<li>Create Project:{tProjects('createProject')}</li>
</ul>
</Card>
<CardclassName="p-6">
<Typographytype="h2"className="mb-4">Auth Translations</Typography>
<ulclassName="space-y-2">
<li>Sign In:{tAuth('signIn')}</li>
<li>Sign Up:{tAuth('signUp')}</li>
<li>Email:{tAuth('email')}</li>
<li>Password:{tAuth('password')}</li>
</ul>
</Card>
<CardclassName="p-6 bg-blue-50 dark:bg-blue-950">
<Typographytype="h3"className="mb-2">How to Test</Typography>
<olclassName="list-decimal list-inside space-y-1 text-sm">
<li>Check the language switcher in the navbar (top right, globe icon 🌐)</li>
<li>Click to switch between English and 中文</li>
<li>Observe how the text on this page changes</li>
<li>Try switching languages: /i18n-test (English) vs /zh/i18n-test (Chinese)</li>
</ol>
</Card>
</div>
);
}
"use client";
import{Card,Typography}from'@stackframe/stack-ui';
import{useTranslations}from'next-intl';
exportconstmetadata={
title:"i18n Test",
};
exportdefaultfunctionI18nTestPage(){
constt=useTranslations('common');
consttProjects=useTranslations('projects');
consttAuth=useTranslations('auth');
return(
<divclassName="container mx-auto p-8 space-y-6">
<Typographytype="h1">i18n Test Page</Typography>
<CardclassName="p-6">
<Typographytype="h2"className="mb-4">Common Translations</Typography>
<ulclassName="space-y-2">
<li>Dashboard:{t('dashboard')}</li>
<li>Projects:{t('projects')}</li>
<li>Settings:{t('settings')}</li>
<li>Save:{t('save')}</li>
<li>Cancel:{t('cancel')}</li>
<li>Loading:{t('loading')}</li>
</ul>
</Card>
<CardclassName="p-6">
<Typographytype="h2"className="mb-4">Projects Translations</Typography>
<ulclassName="space-y-2">
<li>Title:{tProjects('title')}</li>
<li>New Project:{tProjects('newProject')}</li>
<li>Create Project:{tProjects('createProject')}</li>
</ul>
</Card>
<CardclassName="p-6">
<Typographytype="h2"className="mb-4">Auth Translations</Typography>
<ulclassName="space-y-2">
<li>Sign In:{tAuth('signIn')}</li>
<li>Sign Up:{tAuth('signUp')}</li>
<li>Email:{tAuth('email')}</li>
<li>Password:{tAuth('password')}</li>
</ul>
</Card>
<CardclassName="p-6 bg-blue-50 dark:bg-blue-950">
<Typographytype="h3"className="mb-2">How to Test</Typography>
<olclassName="list-decimal list-inside space-y-1 text-sm">
<li>Check the language switcher in the navbar (top right, globe icon 🌐)</li>
<li>Click to switch between English and 中文</li>
<li>Observe how the text on this page changes</li>
<li>Try switching languages: /i18n-test (English) vs /zh/i18n-test (Chinese)</li>
</ol>
</Card>
</div>
);
}
🤖 Prompt for AI Agents
In apps/dashboard/src/app/(main)/i18n-test/page.tsx lines 1-60, the componentuses client-side hooks useTranslations but lacks the "use client" directive; addthe literal string "use client" as the very first line of the file so thecomponent is compiled as a Client Component and the hooks will work, oralternatively convert the file to a Server Component by removing the hooks andusing the async getTranslations API instead (pick one approach and applyconsistently).

Comment on lines 72 to 76
accessorKey:"status",
header:({ column})=><DataTableColumnHeadercolumn={column}columnTitle="Status"/>,
header:({ column})=><DataTableColumnHeadercolumn={column}columnTitle={t('status')}/>,
cell:({ row})=><BadgeCellbadges={[row.original.status]}/>,
filterFn:standardFilterFn,
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue |🟠 Major

Localize status badge text

UI shows raw values ('valid', 'expired', 'revoked'). Display translated labels while keeping raw values for filtering.

   {     accessorKey: "status",     header: ({ column }) => <DataTableColumnHeader column={column} columnTitle={t('status')} />,-    cell: ({ row }) => <BadgeCell badges={[row.original.status]} />,+    cell: ({ row }) => <BadgeCell badges={[tStatus(row.original.status)]} />,     filterFn: standardFilterFn,   },

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/dashboard/src/components/data-table/api-key-table.tsx around lines 72 to76, the status column currently renders raw status values ("valid", "expired","revoked") in the BadgeCell; change the cell renderer to show a localized labelwhile leaving the underlying raw value untouched for filtering. Use the i18ntranslate function (t) to map row.original.status to a user-facing string (e.g.,t(`status.${row.original.status}`) or similar key) and pass that translatedstring into BadgeCell (badges=[translatedLabel]), keeping filterFn as is sofiltering still operates on the raw status value. Ensure you import/obtain the tfunction in this file and handle any missing translation fallback (e.g., defaultto the raw status).

Comment on lines +33 to 48
constt=useTranslations('common.dataTable.dialogs.removeUser');
return<ActionDialog
title
danger
open={props.open}
onOpenChange={props.onOpenChange}
okButton={{
label:"Remove user from team",
label:t('buttonLabel'),
onClick:async()=>{awaitprops.team.removeUser(props.user.id);}
}}
cancelButton
confirmText="I understand this will cause the user to lose access to the team."
confirmText={t('confirmText')}
>
{`Are you sure you want to remove the user "${props.user.displayName}" from the team "${props.team.displayName}"?`}
{/* eslint-disable-next-line @typescript-eslint/no-unnecessary-condition */}
{t('description',{userName:props.user.displayName??props.user.primaryEmail??props.user.id,teamName:props.team.displayName??props.team.id})}
</ActionDialog>;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue |🟠 Major

Localize dialog title and Cancel label

ActionDialog defaults (“Alert”/“Confirmation”, “Cancel”) stay English. Pass translated title and cancel label.

-  const t = useTranslations('common.dataTable.dialogs.removeUser');+  const t = useTranslations('common.dataTable.dialogs.removeUser');+  const tCommon = useTranslations('common.actions');   return <ActionDialog-    title+    title={t('title')}     danger     open={props.open}     onOpenChange={props.onOpenChange}     okButton={{       label: t('buttonLabel'),       onClick: async () => { await props.team.removeUser(props.user.id); }     }}-    cancelButton+    cancelButton={{ label: tCommon('cancel') }}     confirmText={t('confirmText')}   >
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
constt=useTranslations('common.dataTable.dialogs.removeUser');
return<ActionDialog
title
danger
open={props.open}
onOpenChange={props.onOpenChange}
okButton={{
label:"Remove user from team",
label:t('buttonLabel'),
onClick:async()=>{awaitprops.team.removeUser(props.user.id);}
}}
cancelButton
confirmText="I understand this will cause the user to lose access to the team."
confirmText={t('confirmText')}
>
{`Are you sure you want to remove the user "${props.user.displayName}" from the team "${props.team.displayName}"?`}
{/* eslint-disable-next-line @typescript-eslint/no-unnecessary-condition */}
{t('description',{userName:props.user.displayName??props.user.primaryEmail??props.user.id,teamName:props.team.displayName??props.team.id})}
</ActionDialog>;
constt=useTranslations('common.dataTable.dialogs.removeUser');
consttCommon=useTranslations('common.actions');
return<ActionDialog
title={t('title')}
danger
open={props.open}
onOpenChange={props.onOpenChange}
okButton={{
label:t('buttonLabel'),
onClick:async()=>{awaitprops.team.removeUser(props.user.id);}
}}
cancelButton={{label:tCommon('cancel')}}
confirmText={t('confirmText')}
>
{/* eslint-disable-next-line @typescript-eslint/no-unnecessary-condition */}
{t('description',{
userName:props.user.displayName??props.user.primaryEmail??props.user.id,
teamName:props.team.displayName??props.team.id
})}
</ActionDialog>;
🤖 Prompt for AI Agents
In apps/dashboard/src/components/data-table/team-member-table.tsx around lines33 to 48, the ActionDialog is using default English title and Cancel label; passlocalized strings from the translations hook by setting the title prop tot('title') and replacing cancelButton with an object that includes label:t('cancelLabel') (e.g., cancelButton={{ label: t('cancelLabel') }}), keepingexisting okButton and other props unchanged.

Comment on lines +22 to +28
consthandleLanguageChange=(newLocale:string)=>{
// Set locale cookie (1 year expiry)
document.cookie=`NEXT_LOCALE=${newLocale}; path=/; max-age=31536000; SameSite=Lax`;

// Refresh the page to apply new locale
router.refresh();
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue |🟡 Minor

AddSecure flag to locale cookie in production.

The cookie is set without theSecure flag, which means it can be transmitted over unencrypted HTTP connections. While this is acceptable in development, production deployments should enforce HTTPS-only transmission.

Apply this change to conditionally add theSecure flag:

   const handleLanguageChange = (newLocale: string) => {-    // Set locale cookie (1 year expiry)-    document.cookie = `NEXT_LOCALE=${newLocale}; path=/; max-age=31536000; SameSite=Lax`;+    // Set locale cookie (1 year expiry)+    const secure = window.location.protocol === 'https:' ? '; Secure' : '';+    document.cookie = `NEXT_LOCALE=${newLocale}; path=/; max-age=31536000; SameSite=Lax${secure}`;      // Refresh the page to apply new locale     router.refresh();   };
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
consthandleLanguageChange=(newLocale:string)=>{
// Set locale cookie (1 year expiry)
document.cookie=`NEXT_LOCALE=${newLocale}; path=/; max-age=31536000; SameSite=Lax`;
// Refresh the page to apply new locale
router.refresh();
};
consthandleLanguageChange=(newLocale:string)=>{
// Set locale cookie (1 year expiry)
constsecure=window.location.protocol==='https:' ?'; Secure' :'';
document.cookie=`NEXT_LOCALE=${newLocale}; path=/; max-age=31536000; SameSite=Lax${secure}`;
// Refresh the page to apply new locale
router.refresh();
};
🤖 Prompt for AI Agents
In apps/dashboard/src/components/language-switcher.tsx around lines 22 to 28,the locale cookie is set without the Secure flag; update the cookie string toappend "; Secure" when running in production/over HTTPS. Modify the handler tocompute a secureSuffix (e.g., process.env.NODE_ENV === 'production' ? '; Secure': '' or check window.location.protocol === 'https:') and include it in thedocument.cookie assignment so the cookie becomes `NEXT_LOCALE=...; path=/;max-age=31536000; SameSite=Lax${secureSuffix}` ensuring Secure is only added inproduction/secure contexts.

Comment on lines +14 to +16
open -a Docker
echo" Waiting for Docker to start..."
sleep 15
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue |🟠 Major

Mac-only Docker start; add cross‑platform handling

open -a Docker works only on macOS. Add OS checks; on Linux try systemd; otherwise instruct user.

-    echo "   Starting Docker Desktop..."-    open -a Docker+    echo "   Starting Docker..."+    if [[ "$OSTYPE" == "darwin"* ]]; then+      open -a Docker+    elif command -v systemctl >/dev/null 2>&1; then+      sudo systemctl start docker || true+    else+      echo "   Please start Docker manually for your platform."+    fi

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In scripts/start-dev.sh around lines 14–16, the script currently runs themacOS-only command open -a Docker; replace this with an OS check (useuname/OSTYPE): if macOS, keep open -a Docker; if Linux, attempt to start thedaemon with sudo systemctl start docker (and fall back to sudo service dockerstart if systemctl not available); if neither, print a clear instruction for theuser to start Docker (e.g., on Windows ask them to launch Docker Desktop). Afterattempting to start Docker, wait for the daemon to be ready by polling alightweight Docker command (docker info or docker ps) with a timeout/intervalinstead of a fixed sleep, and exit with a helpful error if Docker never becomesavailable.

Comment on lines +28 to +41
CONFLICTING_CONTAINERS=$(docker ps --format'{{.Names}}'| grep -v"^stack-dependencies"|| true)
if [!-z"$CONFLICTING_CONTAINERS" ];then
echo"⚠️ Found non-Stack containers that might conflict:"
echo"$CONFLICTING_CONTAINERS"
echo""
read -p"Do you want to stop these containers? (y/N):" -n 1 -r
echo
if [[$REPLY=~ ^[Yy]$ ]];then
echo"$CONFLICTING_CONTAINERS"| xargs docker stop2>/dev/null||true
echo"✅ Stopped conflicting containers"
else
echo"⚠️ Continuing with existing containers (may cause port conflicts)"
fi
fi
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue |🟠 Major

Stopping all non-Stack containers is too broad; detect only port conflicts

Current logic may stop unrelated containers. Filter by containers publishing to dev ports before prompting.

-CONFLICTING_CONTAINERS=$(docker ps --format '{{.Names}}' | grep -v "^stack-dependencies" || true)+DEV_PORTS_REGEX='(8100|8101|8102|8103|8104|8105|8106|8113|8116|8117|8118)'+# Capture only containers binding to the ports above+CONFLICTING_CONTAINERS=$(docker ps --format '{{.ID}} {{.Names}} {{.Ports}}' \+  | awk -v re="$DEV_PORTS_REGEX" '$0 ~ re { print $2 }' || true)@@-    read -p "Do you want to stop these containers? (y/N): " -n 1 -r+    if [ -t 0 ]; then+      read -p "Do you want to stop these containers? (y/N): " -n 1 -r+    else+      REPLY="n"+    fi@@-        echo "$CONFLICTING_CONTAINERS" | xargs docker stop 2>/dev/null || true+        echo "$CONFLICTING_CONTAINERS" | xargs -r docker stop 2>/dev/null || true
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
CONFLICTING_CONTAINERS=$(docker ps --format'{{.Names}}'| grep -v"^stack-dependencies"|| true)
if [!-z"$CONFLICTING_CONTAINERS" ];then
echo"⚠️ Found non-Stack containers that might conflict:"
echo"$CONFLICTING_CONTAINERS"
echo""
read -p"Do you want to stop these containers? (y/N):" -n 1 -r
echo
if [[$REPLY=~ ^[Yy]$ ]];then
echo"$CONFLICTING_CONTAINERS"| xargs docker stop2>/dev/null||true
echo"✅ Stopped conflicting containers"
else
echo"⚠️ Continuing with existing containers (may cause port conflicts)"
fi
fi
DEV_PORTS_REGEX='(8100|8101|8102|8103|8104|8105|8106|8113|8116|8117|8118)'
# Capture only containers binding to the ports above
CONFLICTING_CONTAINERS=$(docker ps --format'{{.ID}} {{.Names}} {{.Ports}}' \
| awk -v re="$DEV_PORTS_REGEX"'$0 ~ re { print $2 }'|| true)
if [!-z"$CONFLICTING_CONTAINERS" ];then
echo"⚠️ Found non-Stack containers that might conflict:"
echo"$CONFLICTING_CONTAINERS"
echo""
if [-t 0 ];then
read -p"Do you want to stop these containers? (y/N):" -n 1 -r
else
REPLY="n"
fi
echo
if [[$REPLY=~ ^[Yy]$ ]];then
echo"$CONFLICTING_CONTAINERS"| xargs -r docker stop2>/dev/null||true
echo"✅ Stopped conflicting containers"
else
echo"⚠️ Continuing with existing containers (may cause port conflicts)"
fi
fi
🤖 Prompt for AI Agents
In scripts/start-dev.sh around lines 28-41, instead of stopping all non-Stackcontainers, detect only containers that publish ports conflicting with our devports: define a DEV_PORTS list (or read from env/compose), list runningcontainers with their published ports (docker ps --format '{{.Names}}{{.Ports}}'), filter that output for any container whose published ports matchany port in DEV_PORTS, and assign those names to CONFLICTING_CONTAINERS; keepthe existing prompt/stop flow but operate only on this filtered set so unrelatedcontainers are not stopped.

@fuwenbinfuwenbin marked this pull request as draftOctober 10, 2025 10:35
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@vercelvercel[bot]vercel[bot] left review comments

@ellipsis-devellipsis-dev[bot]ellipsis-dev[bot] left review comments

@coderabbitaicoderabbitai[bot]coderabbitai[bot] left review comments

+1 more reviewer

@recursemlrecurseml[bot]recurseml[bot] left review comments

Reviewers whose approvals may not affect merge requirements

At least 1 approving review is required to merge this pull request.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

2 participants

@fuwenbin@CLAassistant

[8]ページ先頭

©2009-2025 Movatter.jp