From 39bde38fccecdd2ddafcc6fa7e6196a54558a22d Mon Sep 17 00:00:00 2001 From: Steven Tey Date: Wed, 1 Jan 2025 16:54:00 -0800 Subject: [PATCH 1/2] Add i18n --- apps/web/app/layout.tsx | 12 +- apps/web/lib/i18n/request.ts | 12 + apps/web/messages/en.json | 32 ++ apps/web/next.config.js | 469 +++++++++--------- apps/web/package.json | 1 + .../web/ui/layout/sidebar/app-sidebar-nav.tsx | 387 ++++++++------- pnpm-lock.yaml | 71 ++- 7 files changed, 524 insertions(+), 460 deletions(-) create mode 100644 apps/web/lib/i18n/request.ts create mode 100644 apps/web/messages/en.json diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index 591df371c6..c6373dabdc 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -1,22 +1,28 @@ import { geistMono, inter, satoshi } from "@/styles/fonts"; import "@/styles/globals.css"; import { cn, constructMetadata } from "@dub/utils"; +import { NextIntlClientProvider } from "next-intl"; +import { getLocale, getMessages } from "next-intl/server"; import RootProviders from "./providers"; - export const metadata = constructMetadata(); -export default function RootLayout({ +export default async function RootLayout({ children, }: { children: React.ReactNode; }) { + const locale = await getLocale(); + const messages = await getMessages(); + return ( - {children} + + {children} + ); diff --git a/apps/web/lib/i18n/request.ts b/apps/web/lib/i18n/request.ts new file mode 100644 index 0000000000..11360af6f4 --- /dev/null +++ b/apps/web/lib/i18n/request.ts @@ -0,0 +1,12 @@ +import { getRequestConfig } from "next-intl/server"; + +export default getRequestConfig(async () => { + // Provide a static locale, fetch a user setting, + // read from `cookies()`, `headers()`, etc. + const locale = "en"; + + return { + locale, + messages: (await import(`../../messages/${locale}.json`)).default, + }; +}); diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json new file mode 100644 index 0000000000..02f390f21a --- /dev/null +++ b/apps/web/messages/en.json @@ -0,0 +1,32 @@ +{ + "AppSidebarNav": { + "links": "Links", + "analytics": "Analytics", + "events": "Events", + "settings": "Settings", + "workspace": "Workspace", + "general": "General", + "billing": "Billing", + "domains": "Domains", + "library": "Library", + "people": "People", + "integrations": "Integrations", + "security": "Security", + "developer": "Developer", + "apiKeys": "API Keys", + "oauthApps": "OAuth Apps", + "webhooks": "Webhooks", + "notifications": "Notifications", + "programs": "Programs", + "affiliate": "Affiliate", + "overview": "Overview", + "partners": "Partners", + "sales": "Sales", + "payouts": "Payouts", + "branding": "Branding", + "resources": "Resources", + "configuration": "Configuration", + "account": "Account", + "referrals": "Referrals" + } +} diff --git a/apps/web/next.config.js b/apps/web/next.config.js index bffc732310..10a597bb64 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -1,5 +1,6 @@ const { PrismaPlugin } = require("@prisma/nextjs-monorepo-workaround-plugin"); const { withAxiom } = require("next-axiom"); +const createNextIntlPlugin = require("next-intl/plugin"); const REDIRECT_SEGMENTS = [ "pricing", @@ -10,240 +11,244 @@ const REDIRECT_SEGMENTS = [ "_static", ]; +const withNextIntl = createNextIntlPlugin("./lib/i18n/request.ts"); + /** @type {import('next').NextConfig} */ -module.exports = withAxiom({ - reactStrictMode: false, - transpilePackages: ["shiki", "@dub/prisma"], - experimental: { - serverComponentsExternalPackages: [ - "@react-email/components", - "@react-email/render", - "@react-email/tailwind", - ], - }, - webpack: (config, { webpack, isServer }) => { - if (isServer) { - config.plugins.push( - // mute errors for unused typeorm deps - new webpack.IgnorePlugin({ - resourceRegExp: - /(^@google-cloud\/spanner|^@mongodb-js\/zstd|^aws-crt|^aws4$|^pg-native$|^mongodb-client-encryption$|^@sap\/hana-client$|^@sap\/hana-client\/extension\/Stream$|^snappy$|^react-native-sqlite-storage$|^bson-ext$|^cardinal$|^kerberos$|^hdb-pool$|^sql.js$|^sqlite3$|^better-sqlite3$|^ioredis$|^typeorm-aurora-data-api-driver$|^pg-query-stream$|^oracledb$|^mysql$|^snappy\/package\.json$|^cloudflare:sockets$)/, - }), - ); +module.exports = withAxiom( + withNextIntl({ + reactStrictMode: false, + transpilePackages: ["shiki", "@dub/prisma"], + experimental: { + serverComponentsExternalPackages: [ + "@react-email/components", + "@react-email/render", + "@react-email/tailwind", + ], + }, + webpack: (config, { webpack, isServer }) => { + if (isServer) { + config.plugins.push( + // mute errors for unused typeorm deps + new webpack.IgnorePlugin({ + resourceRegExp: + /(^@google-cloud\/spanner|^@mongodb-js\/zstd|^aws-crt|^aws4$|^pg-native$|^mongodb-client-encryption$|^@sap\/hana-client$|^@sap\/hana-client\/extension\/Stream$|^snappy$|^react-native-sqlite-storage$|^bson-ext$|^cardinal$|^kerberos$|^hdb-pool$|^sql.js$|^sqlite3$|^better-sqlite3$|^ioredis$|^typeorm-aurora-data-api-driver$|^pg-query-stream$|^oracledb$|^mysql$|^snappy\/package\.json$|^cloudflare:sockets$)/, + }), + ); - config.plugins = [...config.plugins, new PrismaPlugin()]; - } + config.plugins = [...config.plugins, new PrismaPlugin()]; + } - config.module = { - ...config.module, - exprContextCritical: false, - }; + config.module = { + ...config.module, + exprContextCritical: false, + }; - return config; - }, - images: { - remotePatterns: [ - { - hostname: "assets.dub.co", // for Dub's static assets - }, - { - hostname: "dubassets.com", // for Dub's user generated images - }, - { - hostname: "dev.dubassets.com", // dev bucket - }, - { - hostname: "www.google.com", - }, - { - hostname: "avatar.vercel.sh", - }, - { - hostname: "faisalman.github.io", - }, - { - hostname: "api.dicebear.com", - }, - { - hostname: "pbs.twimg.com", - }, - { - hostname: "lh3.googleusercontent.com", - }, - { - hostname: "avatars.githubusercontent.com", - }, - { - hostname: "media.cleanshot.cloud", // only for staging purposes - }, - ], - }, - async headers() { - return [ - { - source: "/:path*", - headers: [ - { - key: "Referrer-Policy", - value: "no-referrer-when-downgrade", - }, - { - key: "X-DNS-Prefetch-Control", - value: "on", - }, - { - key: "X-Frame-Options", - value: "DENY", - }, - ], - }, - { - source: "/embed/:path*", - headers: [ - { - key: "Content-Security-Policy", - value: "frame-ancestors *", - }, - ], - }, - ]; - }, - async redirects() { - return [ - { - source: "/", - has: [ - { - type: "host", - value: "app.dub.sh", - }, - ], - destination: "https://app.dub.co", - permanent: true, - statusCode: 301, - }, - { - source: "/:path*", - has: [ - { - type: "host", - value: "app.dub.sh", - }, - ], - destination: "https://app.dub.co/:path*", - permanent: true, - statusCode: 301, - }, - ...REDIRECT_SEGMENTS.map( - (segment) => ( - { - source: `/${segment}`, - has: [ - { - type: "host", - value: "dub.sh", - }, - ], - destination: `https://dub.co/${segment}`, - permanent: true, - statusCode: 301, - }, - { - source: `/${segment}/:path*`, - has: [ - { - type: "host", - value: "dub.sh", - }, - ], - destination: `https://dub.co/${segment}/:path*`, - permanent: true, - statusCode: 301, - } + return config; + }, + images: { + remotePatterns: [ + { + hostname: "assets.dub.co", // for Dub's static assets + }, + { + hostname: "dubassets.com", // for Dub's user generated images + }, + { + hostname: "dev.dubassets.com", // dev bucket + }, + { + hostname: "www.google.com", + }, + { + hostname: "avatar.vercel.sh", + }, + { + hostname: "faisalman.github.io", + }, + { + hostname: "api.dicebear.com", + }, + { + hostname: "pbs.twimg.com", + }, + { + hostname: "lh3.googleusercontent.com", + }, + { + hostname: "avatars.githubusercontent.com", + }, + { + hostname: "media.cleanshot.cloud", // only for staging purposes + }, + ], + }, + async headers() { + return [ + { + source: "/:path*", + headers: [ + { + key: "Referrer-Policy", + value: "no-referrer-when-downgrade", + }, + { + key: "X-DNS-Prefetch-Control", + value: "on", + }, + { + key: "X-Frame-Options", + value: "DENY", + }, + ], + }, + { + source: "/embed/:path*", + headers: [ + { + key: "Content-Security-Policy", + value: "frame-ancestors *", + }, + ], + }, + ]; + }, + async redirects() { + return [ + { + source: "/", + has: [ + { + type: "host", + value: "app.dub.sh", + }, + ], + destination: "https://app.dub.co", + permanent: true, + statusCode: 301, + }, + { + source: "/:path*", + has: [ + { + type: "host", + value: "app.dub.sh", + }, + ], + destination: "https://app.dub.co/:path*", + permanent: true, + statusCode: 301, + }, + ...REDIRECT_SEGMENTS.map( + (segment) => ( + { + source: `/${segment}`, + has: [ + { + type: "host", + value: "dub.sh", + }, + ], + destination: `https://dub.co/${segment}`, + permanent: true, + statusCode: 301, + }, + { + source: `/${segment}/:path*`, + has: [ + { + type: "host", + value: "dub.sh", + }, + ], + destination: `https://dub.co/${segment}/:path*`, + permanent: true, + statusCode: 301, + } + ), ), - ), - { - source: "/metatags", - has: [ - { - type: "host", - value: "dub.sh", - }, - ], - destination: "https://dub.co/tools/metatags", - permanent: true, - statusCode: 301, - }, - { - source: "/metatags", - has: [ - { - type: "host", - value: "dub.co", - }, - ], - destination: "/tools/metatags", - permanent: true, - statusCode: 301, - }, - { - source: "/", - has: [ - { - type: "host", - value: "staging.dub.sh", - }, - ], - destination: "https://dub.co", - permanent: true, - statusCode: 301, - }, - { - source: "/", - has: [ - { - type: "host", - value: "preview.dub.sh", - }, - ], - destination: "https://preview.dub.co", - permanent: true, - statusCode: 301, - }, - { - source: "/", - has: [ - { - type: "host", - value: "admin.dub.sh", - }, - ], - destination: "https://admin.dub.co", - permanent: true, - statusCode: 301, - }, - ]; - }, - async rewrites() { - return [ - // for posthog proxy - { - source: "/_proxy/posthog/ingest/static/:path*", - destination: "https://us-assets.i.posthog.com/static/:path*", - }, - { - source: "/_proxy/posthog/ingest/:path*", - destination: "https://us.i.posthog.com/:path*", - }, - // for plausible proxy - { - source: "/_proxy/plausible/script.js", - destination: "https://plausible.io/js/script.js", - }, - { - source: "/_proxy/plausible/event", - destination: "https://plausible.io/api/event", - }, - ]; - }, -}); + { + source: "/metatags", + has: [ + { + type: "host", + value: "dub.sh", + }, + ], + destination: "https://dub.co/tools/metatags", + permanent: true, + statusCode: 301, + }, + { + source: "/metatags", + has: [ + { + type: "host", + value: "dub.co", + }, + ], + destination: "/tools/metatags", + permanent: true, + statusCode: 301, + }, + { + source: "/", + has: [ + { + type: "host", + value: "staging.dub.sh", + }, + ], + destination: "https://dub.co", + permanent: true, + statusCode: 301, + }, + { + source: "/", + has: [ + { + type: "host", + value: "preview.dub.sh", + }, + ], + destination: "https://preview.dub.co", + permanent: true, + statusCode: 301, + }, + { + source: "/", + has: [ + { + type: "host", + value: "admin.dub.sh", + }, + ], + destination: "https://admin.dub.co", + permanent: true, + statusCode: 301, + }, + ]; + }, + async rewrites() { + return [ + // for posthog proxy + { + source: "/_proxy/posthog/ingest/static/:path*", + destination: "https://us-assets.i.posthog.com/static/:path*", + }, + { + source: "/_proxy/posthog/ingest/:path*", + destination: "https://us.i.posthog.com/:path*", + }, + // for plausible proxy + { + source: "/_proxy/plausible/script.js", + destination: "https://plausible.io/js/script.js", + }, + { + source: "/_proxy/plausible/event", + destination: "https://plausible.io/api/event", + }, + ]; + }, + }), +); diff --git a/apps/web/package.json b/apps/web/package.json index 00eeafbc0c..92435cee72 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -80,6 +80,7 @@ "next": "14.3.0-canary.42", "next-auth": "^4.24.4", "next-axiom": "^1.6.0", + "next-intl": "^3.26.3", "next-plausible": "^3.12.0", "next-safe-action": "7.9.9", "node-html-parser": "^6.1.4", diff --git a/apps/web/ui/layout/sidebar/app-sidebar-nav.tsx b/apps/web/ui/layout/sidebar/app-sidebar-nav.tsx index 94c586b403..6505dc4d90 100644 --- a/apps/web/ui/layout/sidebar/app-sidebar-nav.tsx +++ b/apps/web/ui/layout/sidebar/app-sidebar-nav.tsx @@ -21,6 +21,7 @@ import { } from "@dub/ui/icons"; import { Session } from "next-auth"; import { useSession } from "next-auth/react"; +import { useTranslations } from "next-intl"; import { useParams, usePathname } from "next/navigation"; import { ReactNode, useMemo } from "react"; import UserSurveyButton from "../user-survey"; @@ -40,199 +41,211 @@ const NAV_AREAS: SidebarNavAreas<{ session?: Session | null; }> = { // Top-level - default: ({ slug, queryString, programs }) => ({ - showSwitcher: true, - showNews: true, - direction: "left", - content: [ - { - items: [ - { - name: "Links", - icon: Hyperlink, - href: `/${slug}`, - exact: true, - }, - { - name: "Analytics", - icon: LinesY, - href: `/${slug}/analytics${queryString}`, - }, - { - name: "Events", - icon: CursorRays, - href: `/${slug}/events${queryString}`, - }, - { - name: "Settings", - icon: Gear, - href: `/${slug}/settings`, - }, - ], - }, - ...(programs?.length - ? [ + default: ({ slug, queryString, programs }) => { + const t = useTranslations("AppSidebarNav"); + + return { + showSwitcher: true, + showNews: true, + direction: "left", + content: [ + { + items: [ + { + name: t("links"), + icon: Hyperlink, + href: `/${slug}`, + exact: true, + }, + { + name: t("analytics"), + icon: LinesY, + href: `/${slug}/analytics${queryString}`, + }, { - name: "Programs", - items: [ - { - name: "Affiliate", - icon: ConnectedDots4, - href: `/${slug}/programs/${programs[0].id}`, - items: [ - { - name: "Overview", - href: `/${slug}/programs/${programs[0].id}`, - exact: true, - }, - { - name: "Partners", - href: `/${slug}/programs/${programs[0].id}/partners`, - }, - { - name: "Sales", - href: `/${slug}/programs/${programs[0].id}/sales`, - }, - { - name: "Payouts", - href: `/${slug}/programs/${programs[0].id}/payouts`, - }, - { - name: "Branding", - href: `/${slug}/programs/${programs[0].id}/branding`, - }, - { - name: "Resources", - href: `/${slug}/programs/${programs[0].id}/resources`, - }, - { - name: "Configuration", - href: `/${slug}/programs/${programs[0].id}/settings`, - }, - ], - }, - ], + name: t("events"), + icon: CursorRays, + href: `/${slug}/events${queryString}`, }, - ] - : []), - ], - }), + { + name: t("settings"), + icon: Gear, + href: `/${slug}/settings`, + }, + ], + }, + ...(programs?.length + ? [ + { + name: t("programs"), + items: [ + { + name: t("affiliate"), + icon: ConnectedDots4, + href: `/${slug}/programs/${programs[0].id}`, + items: [ + { + name: t("overview"), + href: `/${slug}/programs/${programs[0].id}`, + exact: true, + }, + { + name: t("partners"), + href: `/${slug}/programs/${programs[0].id}/partners`, + }, + { + name: t("sales"), + href: `/${slug}/programs/${programs[0].id}/sales`, + }, + { + name: t("payouts"), + href: `/${slug}/programs/${programs[0].id}/payouts`, + }, + { + name: t("branding"), + href: `/${slug}/programs/${programs[0].id}/branding`, + }, + { + name: t("resources"), + href: `/${slug}/programs/${programs[0].id}/resources`, + }, + { + name: t("configuration"), + href: `/${slug}/programs/${programs[0].id}/settings`, + }, + ], + }, + ], + }, + ] + : []), + ], + }; + }, // Workspace settings - workspaceSettings: ({ slug, flags }) => ({ - title: "Settings", - backHref: `/${slug}`, - content: [ - { - name: "Workspace", - items: [ - { - name: "General", - icon: Gear2, - href: `/${slug}/settings`, - exact: true, - }, - { - name: "Billing", - icon: Receipt2, - href: `/${slug}/settings/billing`, - }, - { - name: "Domains", - icon: Globe, - href: `/${slug}/settings/domains`, - }, - { - name: "Library", - icon: Books2, - href: `/${slug}/settings/library`, - }, - { - name: "People", - icon: Users6, - href: `/${slug}/settings/people`, - }, - { - name: "Integrations", - icon: ConnectedDots, - href: `/${slug}/settings/integrations`, - }, - { - name: "Security", - icon: ShieldCheck, - href: `/${slug}/settings/security`, - }, - ], - }, - { - name: "Developer", - items: [ - { - name: "API Keys", - icon: Key, - href: `/${slug}/settings/tokens`, - }, - { - name: "OAuth Apps", - icon: CubeSettings, - href: `/${slug}/settings/oauth-apps`, - }, - ...(flags?.webhooks - ? [ - { - name: "Webhooks", - icon: Webhook, - href: `/${slug}/settings/webhooks`, - }, - ] - : []), - ], - }, - { - name: "Account", - items: [ - { - name: "Notifications", - icon: CircleInfo, - href: `/${slug}/settings/notifications`, - }, - ], - }, - ], - }), + workspaceSettings: ({ slug, flags }) => { + const t = useTranslations("AppSidebarNav"); + + return { + title: t("settings"), + backHref: `/${slug}`, + content: [ + { + name: t("workspace"), + items: [ + { + name: t("general"), + icon: Gear2, + href: `/${slug}/settings`, + exact: true, + }, + { + name: t("billing"), + icon: Receipt2, + href: `/${slug}/settings/billing`, + }, + { + name: t("domains"), + icon: Globe, + href: `/${slug}/settings/domains`, + }, + { + name: t("library"), + icon: Books2, + href: `/${slug}/settings/library`, + }, + { + name: t("people"), + icon: Users6, + href: `/${slug}/settings/people`, + }, + { + name: t("integrations"), + icon: ConnectedDots, + href: `/${slug}/settings/integrations`, + }, + { + name: t("security"), + icon: ShieldCheck, + href: `/${slug}/settings/security`, + }, + ], + }, + { + name: t("developer"), + items: [ + { + name: t("apiKeys"), + icon: Key, + href: `/${slug}/settings/tokens`, + }, + { + name: t("oauthApps"), + icon: CubeSettings, + href: `/${slug}/settings/oauth-apps`, + }, + ...(flags?.webhooks + ? [ + { + name: t("webhooks"), + icon: Webhook, + href: `/${slug}/settings/webhooks`, + }, + ] + : []), + ], + }, + { + name: t("account"), + items: [ + { + name: t("notifications"), + icon: CircleInfo, + href: `/${slug}/settings/notifications`, + }, + ], + }, + ], + }; + }, // User settings - userSettings: ({ session, slug }) => ({ - title: "Settings", - backHref: `/${slug}`, - content: [ - { - name: "Account", - items: [ - { - name: "General", - icon: Gear2, - href: "/account/settings", - exact: true, - }, - { - name: "Security", - icon: ShieldCheck, - href: "/account/settings/security", - }, - ...(session?.user?.["referralLinkId"] - ? [ - { - name: "Referrals", - icon: Gift, - href: "/account/settings/referrals", - }, - ] - : []), - ], - }, - ], - }), + userSettings: ({ session, slug }) => { + const t = useTranslations("AppSidebarNav"); + + return { + title: t("settings"), + backHref: `/${slug}`, + content: [ + { + name: t("account"), + items: [ + { + name: t("general"), + icon: Gear2, + href: "/account/settings", + exact: true, + }, + { + name: t("security"), + icon: ShieldCheck, + href: "/account/settings/security", + }, + ...(session?.user?.["referralLinkId"] + ? [ + { + name: t("referrals"), + icon: Gift, + href: "/account/settings/referrals", + }, + ] + : []), + ], + }, + ], + }; + }, }; export function AppSidebarNav({ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 28d2bcd75a..ac92acd7d1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -231,6 +231,9 @@ importers: next-axiom: specifier: ^1.6.0 version: 1.6.0(next@14.3.0-canary.42)(react@18.2.0) + next-intl: + specifier: ^3.26.3 + version: 3.26.3(next@14.3.0-canary.42)(react@18.2.0) next-plausible: specifier: ^3.12.0 version: 3.12.0(next@14.3.0-canary.42)(react-dom@18.2.0)(react@18.2.0) @@ -497,7 +500,7 @@ importers: version: link:../../tsconfig tsup: specifier: ^6.1.3 - version: 6.7.0(ts-node@10.9.2)(typescript@5.6.2) + version: 6.7.0(postcss@8.4.31)(typescript@5.6.2) typescript: specifier: ^5.1.6 version: 5.6.2 @@ -829,7 +832,7 @@ importers: version: link:../tsconfig tsup: specifier: ^6.1.3 - version: 6.1.3(typescript@5.1.6) + version: 6.1.3(postcss@8.4.31)(typescript@5.1.6) typescript: specifier: ^5.1.6 version: 5.1.6 @@ -17428,6 +17431,11 @@ packages: engines: {node: '>= 0.6'} dev: false + /negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + dev: false + /neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} dev: false @@ -17471,6 +17479,19 @@ packages: whatwg-fetch: 3.6.19 dev: false + /next-intl@3.26.3(next@14.3.0-canary.42)(react@18.2.0): + resolution: {integrity: sha512-6Y97ODrDsEE1J8cXKMHwg1laLdtkN66QMIqG8BzH4zennJRUNTtM8UMtBDyhfmF6uiZ+xsbWLXmHUgmUymUsfQ==} + peerDependencies: + next: ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0 + dependencies: + '@formatjs/intl-localematcher': 0.5.4 + negotiator: 1.0.0 + next: 14.3.0-canary.42(@babel/core@7.24.5)(@opentelemetry/api@1.8.0)(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + use-intl: 3.26.3(react@18.2.0) + dev: false + /next-plausible@3.12.0(next@14.3.0-canary.42)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-SSkEqKQ6PgR8fx3sYfIAT69k2xuCUXO5ngkSS19CjxY97lAoZxsfZpYednxB4zo0mHYv87JzhPynrdBPlCBVHg==} peerDependencies: @@ -21033,42 +21054,6 @@ packages: - ts-node dev: true - /tsup@6.1.3(typescript@5.1.6): - resolution: {integrity: sha512-eRpBnbfpDFng+EJNTQ90N7QAf4HAGGC7O3buHIjroKWK7D1ibk9/YnR/3cS8HsMU5T+6Oi+cnF+yU5WmCnB//Q==} - engines: {node: '>=14'} - hasBin: true - peerDependencies: - '@swc/core': ^1 - postcss: ^8.4.12 - typescript: ^4.1.0 - peerDependenciesMeta: - '@swc/core': - optional: true - postcss: - optional: true - typescript: - optional: true - dependencies: - bundle-require: 3.1.2(esbuild@0.14.54) - cac: 6.7.14 - chokidar: 3.5.3 - debug: 4.3.4 - esbuild: 0.14.54 - execa: 5.1.1 - globby: 11.1.0 - joycon: 3.1.1 - postcss-load-config: 3.1.4(ts-node@10.9.2) - resolve-from: 5.0.0 - rollup: 2.79.1 - source-map: 0.8.0-beta.0 - sucrase: 3.34.0 - tree-kill: 1.2.2 - typescript: 5.1.6 - transitivePeerDependencies: - - supports-color - - ts-node - dev: true - /tsup@6.7.0(postcss@8.4.31)(typescript@5.6.2): resolution: {integrity: sha512-L3o8hGkaHnu5TdJns+mCqFsDBo83bJ44rlK7e6VdanIvpea4ArPcU3swWGsLVbXak1PqQx/V+SSmFPujBK+zEQ==} engines: {node: '>=14.18'} @@ -21795,6 +21780,16 @@ packages: react: 18.2.0 dev: false + /use-intl@3.26.3(react@18.2.0): + resolution: {integrity: sha512-yY0a2YseO17cKwHA9M6fcpiEJ2Uo81DEU0NOUxNTp6lJVNOuI6nULANPVVht6IFdrYFtlsMmMoc97+Eq9/Tnng==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0 + dependencies: + '@formatjs/fast-memoize': 2.2.0 + intl-messageformat: 10.5.14 + react: 18.2.0 + dev: false + /use-isomorphic-layout-effect@1.1.2(@types/react@18.2.48)(react@18.2.0): resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==} peerDependencies: From be8e9bb7224775ec01bef77d0066be57b56db6b0 Mon Sep 17 00:00:00 2001 From: Steven Tey Date: Wed, 1 Jan 2025 17:12:33 -0800 Subject: [PATCH 2/2] more translations --- apps/web/messages/en.json | 59 +++++++++---------- .../web/ui/layout/sidebar/app-sidebar-nav.tsx | 6 +- apps/web/ui/layout/sidebar/usage.tsx | 11 ++-- 3 files changed, 39 insertions(+), 37 deletions(-) diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 02f390f21a..e7207a8a28 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -1,32 +1,31 @@ { - "AppSidebarNav": { - "links": "Links", - "analytics": "Analytics", - "events": "Events", - "settings": "Settings", - "workspace": "Workspace", - "general": "General", - "billing": "Billing", - "domains": "Domains", - "library": "Library", - "people": "People", - "integrations": "Integrations", - "security": "Security", - "developer": "Developer", - "apiKeys": "API Keys", - "oauthApps": "OAuth Apps", - "webhooks": "Webhooks", - "notifications": "Notifications", - "programs": "Programs", - "affiliate": "Affiliate", - "overview": "Overview", - "partners": "Partners", - "sales": "Sales", - "payouts": "Payouts", - "branding": "Branding", - "resources": "Resources", - "configuration": "Configuration", - "account": "Account", - "referrals": "Referrals" - } + "links": "Links", + "analytics": "Analytics", + "events": "Events", + "settings": "Settings", + "workspace": "Workspace", + "general": "General", + "billing": "Billing", + "domains": "Domains", + "library": "Library", + "people": "People", + "integrations": "Integrations", + "security": "Security", + "developer": "Developer", + "apiKeys": "API Keys", + "oauthApps": "OAuth Apps", + "webhooks": "Webhooks", + "notifications": "Notifications", + "programs": "Programs", + "affiliate": "Affiliate", + "overview": "Overview", + "partners": "Partners", + "sales": "Sales", + "payouts": "Payouts", + "branding": "Branding", + "resources": "Resources", + "configuration": "Configuration", + "account": "Account", + "referrals": "Referrals", + "usage": "Usage" } diff --git a/apps/web/ui/layout/sidebar/app-sidebar-nav.tsx b/apps/web/ui/layout/sidebar/app-sidebar-nav.tsx index 6505dc4d90..dbd464a3ee 100644 --- a/apps/web/ui/layout/sidebar/app-sidebar-nav.tsx +++ b/apps/web/ui/layout/sidebar/app-sidebar-nav.tsx @@ -42,7 +42,7 @@ const NAV_AREAS: SidebarNavAreas<{ }> = { // Top-level default: ({ slug, queryString, programs }) => { - const t = useTranslations("AppSidebarNav"); + const t = useTranslations(); return { showSwitcher: true, @@ -125,7 +125,7 @@ const NAV_AREAS: SidebarNavAreas<{ // Workspace settings workspaceSettings: ({ slug, flags }) => { - const t = useTranslations("AppSidebarNav"); + const t = useTranslations(); return { title: t("settings"), @@ -212,7 +212,7 @@ const NAV_AREAS: SidebarNavAreas<{ // User settings userSettings: ({ session, slug }) => { - const t = useTranslations("AppSidebarNav"); + const t = useTranslations(); return { title: t("settings"), diff --git a/apps/web/ui/layout/sidebar/usage.tsx b/apps/web/ui/layout/sidebar/usage.tsx index 9c707e34ea..5c13325795 100644 --- a/apps/web/ui/layout/sidebar/usage.tsx +++ b/apps/web/ui/layout/sidebar/usage.tsx @@ -13,6 +13,7 @@ import { } from "@dub/utils"; import { AnimatePresence, motion } from "framer-motion"; import { ChevronRight } from "lucide-react"; +import { useTranslations } from "next-intl"; import Link from "next/link"; import { useParams } from "next/navigation"; import { CSSProperties, useMemo, useState } from "react"; @@ -73,6 +74,8 @@ function UsageInner() { const warning = warnings.some((w) => w); + const t = useTranslations(); + return loading || usage !== undefined ? (
@@ -80,14 +83,14 @@ function UsageInner() { className="group flex items-center gap-0.5 text-sm font-normal text-neutral-500 transition-colors hover:text-neutral-700" href={`/${slug}/settings/billing`} > - Usage + {t("usage")}
0 ? (