chore: upgrade next-intl to version 4.0.0 and update type definitions

- Update `next-intl` dependency from version 3.26.5 to 4.0.0
- Modify global type definitions to include new `Locale` and `Messages` types
- Refactor various components and pages to use `Locale` type for params
- Enhance internationalization handling by integrating `hasLocale` checks
- Clean up imports and ensure consistent usage of `next-intl` features
This commit is contained in:
javayhu 2025-03-13 00:23:37 +08:00
parent d7389eef47
commit df1c75f56a
28 changed files with 86 additions and 58 deletions

18
global.d.ts vendored
View File

@ -1,8 +1,14 @@
import en from './messages/en.json'; import { routing } from '@/i18n/routing';
import messages from './messages/en.json';
type Messages = typeof en; /**
* next-intl 4.0.0
declare global { *
// Use type safe message keys with `next-intl` * https://github.com/amannn/next-intl/blob/main/examples/example-app-router/global.d.ts
interface IntlMessages extends Messages {} */
declare module 'next-intl' {
interface AppConfig {
Locale: (typeof routing.locales)[number];
Messages: typeof messages;
}
} }

View File

@ -51,7 +51,7 @@
"mdast-util-toc": "^7.1.0", "mdast-util-toc": "^7.1.0",
"motion": "^12.4.3", "motion": "^12.4.3",
"next": "15.2.1", "next": "15.2.1",
"next-intl": "^3.26.5", "next-intl": "^4.0.0",
"next-plausible": "^3.12.4", "next-plausible": "^3.12.4",
"next-safe-action": "^7.10.4", "next-safe-action": "^7.10.4",
"next-themes": "^0.4.4", "next-themes": "^0.4.4",

37
pnpm-lock.yaml generated
View File

@ -132,8 +132,8 @@ importers:
specifier: 15.2.1 specifier: 15.2.1
version: 15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) version: 15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
next-intl: next-intl:
specifier: ^3.26.5 specifier: ^4.0.0
version: 3.26.5(next@15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0) version: 4.0.0(next@15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)(typescript@5.7.3)
next-plausible: next-plausible:
specifier: ^3.12.4 specifier: ^3.12.4
version: 3.12.4(next@15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0) version: 3.12.4(next@15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
@ -2231,6 +2231,9 @@ packages:
peerDependencies: peerDependencies:
react: ^18.0 || ^19.0 || ^19.0.0-rc react: ^18.0 || ^19.0 || ^19.0.0-rc
'@schummar/icu-type-parser@1.21.5':
resolution: {integrity: sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==}
'@selderee/plugin-htmlparser2@0.11.0': '@selderee/plugin-htmlparser2@0.11.0':
resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==} resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==}
@ -3641,11 +3644,15 @@ packages:
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
next-intl@3.26.5: next-intl@4.0.0:
resolution: {integrity: sha512-EQlCIfY0jOhRldiFxwSXG+ImwkQtDEfQeSOEQp6ieAGSLWGlgjdb/Ck/O7wMfC430ZHGeUKVKax8KGusTPKCgg==} resolution: {integrity: sha512-l+I1PLAFrjzYzrc340n1vssDJ7pP1gtYT1jOWlRWIHkyrPdyosEIHPC+LiqJP4vWvWtCZzzqTn9AaBF+x5Ja8g==}
peerDependencies: peerDependencies:
next: ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 next: ^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 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0
typescript: ^5.0.0
peerDependenciesMeta:
typescript:
optional: true
next-plausible@3.12.4: next-plausible@3.12.4:
resolution: {integrity: sha512-cD3+ixJxf8yBYvsideTxqli3fvrB7R4BXcvsNJz8Sm2X1QN039WfiXjCyNWkub4h5++rRs6fHhchUMnOuJokcg==} resolution: {integrity: sha512-cD3+ixJxf8yBYvsideTxqli3fvrB7R4BXcvsNJz8Sm2X1QN039WfiXjCyNWkub4h5++rRs6fHhchUMnOuJokcg==}
@ -4518,6 +4525,11 @@ packages:
peerDependencies: peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0
use-intl@4.0.0:
resolution: {integrity: sha512-/fmC7haEMVNa0isXGRGUir56fD4I9LRnOgbeBmji+bow6U8pE7WD+2X2sjqh+0h3yJ0T36PA6JXZ6PlVeRyt8w==}
peerDependencies:
react: ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0
use-sidecar@1.1.3: use-sidecar@1.1.3:
resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -6293,6 +6305,8 @@ snapshots:
dependencies: dependencies:
react: 19.0.0 react: 19.0.0
'@schummar/icu-type-parser@1.21.5': {}
'@selderee/plugin-htmlparser2@0.11.0': '@selderee/plugin-htmlparser2@0.11.0':
dependencies: dependencies:
domhandler: 5.0.3 domhandler: 5.0.3
@ -8054,13 +8068,15 @@ snapshots:
negotiator@1.0.0: {} negotiator@1.0.0: {}
next-intl@3.26.5(next@15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0): next-intl@4.0.0(next@15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)(typescript@5.7.3):
dependencies: dependencies:
'@formatjs/intl-localematcher': 0.5.10 '@formatjs/intl-localematcher': 0.5.10
negotiator: 1.0.0 negotiator: 1.0.0
next: 15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) next: 15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
react: 19.0.0 react: 19.0.0
use-intl: 3.26.5(react@19.0.0) use-intl: 4.0.0(react@19.0.0)
optionalDependencies:
typescript: 5.7.3
next-plausible@3.12.4(next@15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0): next-plausible@3.12.4(next@15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
dependencies: dependencies:
@ -9109,6 +9125,13 @@ snapshots:
intl-messageformat: 10.7.15 intl-messageformat: 10.7.15
react: 19.0.0 react: 19.0.0
use-intl@4.0.0(react@19.0.0):
dependencies:
'@formatjs/fast-memoize': 2.2.6
'@schummar/icu-type-parser': 1.21.5
intl-messageformat: 10.7.15
react: 19.0.0
use-sidecar@1.1.3(@types/react@19.0.9)(react@19.0.0): use-sidecar@1.1.3(@types/react@19.0.9)(react@19.0.0):
dependencies: dependencies:
detect-node-es: 1.1.0 detect-node-es: 1.1.0

View File

@ -14,7 +14,7 @@ import Testimonials from '@/components/blocks/testimonials/testimonials';
import { getTranslations } from 'next-intl/server'; import { getTranslations } from 'next-intl/server';
interface HomePageProps { interface HomePageProps {
params: Promise<{ locale: string }>; params: Promise<{ locale: Locale }>;
} }
export default async function HomePage(props: HomePageProps) { export default async function HomePage(props: HomePageProps) {

View File

@ -3,7 +3,7 @@ import CallToAction2 from '@/components/blocks/call-to-action/call-to-action-2';
import CallToAction3 from '@/components/blocks/call-to-action/call-to-action-3'; import CallToAction3 from '@/components/blocks/call-to-action/call-to-action-3';
interface CallToActionPageProps { interface CallToActionPageProps {
params: Promise<{ locale: string }>; params: Promise<{ locale: Locale }>;
} }
/** /**

View File

@ -6,7 +6,7 @@ import Content5 from '@/components/blocks/content/content-5';
import Content6 from '@/components/blocks/content/content-6'; import Content6 from '@/components/blocks/content/content-6';
interface ContentPageProps { interface ContentPageProps {
params: Promise<{ locale: string }>; params: Promise<{ locale: Locale }>;
} }
export default async function ContentPage(props: ContentPageProps) { export default async function ContentPage(props: ContentPageProps) {

View File

@ -1,7 +1,7 @@
import FAQs from '@/components/blocks/faq/faqs'; import FAQs from '@/components/blocks/faq/faqs';
interface FAQPageProps { interface FAQPageProps {
params: Promise<{ locale: string }>; params: Promise<{ locale: Locale }>;
} }
/** /**

View File

@ -8,7 +8,7 @@ import Features8 from '@/components/blocks/features/features-8';
import Features9 from '@/components/blocks/features/features-9'; import Features9 from '@/components/blocks/features/features-9';
interface FeaturesPageProps { interface FeaturesPageProps {
params: Promise<{ locale: string }>; params: Promise<{ locale: Locale }>;
} }
export default async function FeaturesPage(props: FeaturesPageProps) { export default async function FeaturesPage(props: FeaturesPageProps) {

View File

@ -4,7 +4,7 @@ import HeroSection3 from '@/components/blocks/hero/hero-section-3';
import HeroSection4 from '@/components/blocks/hero/hero-section-4'; import HeroSection4 from '@/components/blocks/hero/hero-section-4';
interface HeroPageProps { interface HeroPageProps {
params: Promise<{ locale: string }>; params: Promise<{ locale: Locale }>;
} }
/** /**

View File

@ -5,7 +5,7 @@ import PricingComparator from '@/components/pricing-comparator';
import { getTranslations } from 'next-intl/server'; import { getTranslations } from 'next-intl/server';
interface PricingPageProps { interface PricingPageProps {
params: Promise<{ locale: string }>; params: Promise<{ locale: Locale }>;
} }
/** /**

View File

@ -4,7 +4,7 @@ import Stats3 from '@/components/blocks/stats/stats-3';
import Stats4 from '@/components/blocks/stats/stats-4'; import Stats4 from '@/components/blocks/stats/stats-4';
interface StatsPageProps { interface StatsPageProps {
params: Promise<{ locale: string }>; params: Promise<{ locale: Locale }>;
} }
/** /**

View File

@ -5,7 +5,7 @@ import Testimonials5 from '@/components/blocks/testimonials/testimonials-5';
import Testimonials6 from '@/components/blocks/testimonials/testimonials-6'; import Testimonials6 from '@/components/blocks/testimonials/testimonials-6';
interface TestimonialsPageProps { interface TestimonialsPageProps {
params: Promise<{ locale: string }>; params: Promise<{ locale: Locale }>;
} }
/** /**

View File

@ -13,7 +13,7 @@ import { getBaseUrl } from '@/lib/urls/get-base-url';
export async function generateMetadata({ export async function generateMetadata({
params, params,
}: { }: {
params: Promise<{ slug: string; locale: string }>; params: Promise<{ slug: string; locale: Locale }>;
}): Promise<Metadata | undefined> { }): Promise<Metadata | undefined> {
const resolvedParams = await params; const resolvedParams = await params;
const { slug, locale } = resolvedParams; const { slug, locale } = resolvedParams;

View File

@ -5,7 +5,7 @@ import PricingComparator from '@/components/pricing-comparator';
import { getTranslations } from 'next-intl/server'; import { getTranslations } from 'next-intl/server';
interface PricingPageProps { interface PricingPageProps {
params: Promise<{ locale: string }>; params: Promise<{ locale: Locale }>;
} }
export default async function PricingPage(props: PricingPageProps) { export default async function PricingPage(props: PricingPageProps) {

View File

@ -4,8 +4,7 @@ import { routing } from '@/i18n/routing';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { GeistMono } from 'geist/font/mono'; import { GeistMono } from 'geist/font/mono';
import { GeistSans } from 'geist/font/sans'; import { GeistSans } from 'geist/font/sans';
import { NextIntlClientProvider } from 'next-intl'; import { Locale, hasLocale, NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { Toaster } from 'sonner'; import { Toaster } from 'sonner';
@ -15,7 +14,7 @@ import '@/styles/globals.css';
interface LocaleLayoutProps { interface LocaleLayoutProps {
children: ReactNode; children: ReactNode;
params: Promise<{ locale: string }>; params: Promise<{ locale: Locale }>;
} }
/** /**
@ -32,13 +31,10 @@ export default async function LocaleLayout({
const { locale } = await params; const { locale } = await params;
// Ensure that the incoming `locale` is valid // Ensure that the incoming `locale` is valid
if (!routing.locales.includes(locale as any)) { if (!hasLocale(routing.locales, locale)) {
notFound(); notFound();
} }
// Providing all messages to the client side
const messages = await getMessages();
return ( return (
<html lang={locale} suppressHydrationWarning> <html lang={locale} suppressHydrationWarning>
<body <body
@ -53,7 +49,7 @@ export default async function LocaleLayout({
GeistMono.variable GeistMono.variable
)} )}
> >
<NextIntlClientProvider messages={messages}> <NextIntlClientProvider>
<Providers> <Providers>
{children} {children}

View File

@ -8,9 +8,9 @@ import {
SelectValue, SelectValue,
} from '@/components/ui/select'; } from '@/components/ui/select';
import { useLocalePathname, useLocaleRouter } from '@/i18n/navigation'; import { useLocalePathname, useLocaleRouter } from '@/i18n/navigation';
import { DEFAULT_LOCALE, Locale, LOCALE_LIST, routing } from '@/i18n/routing'; import { DEFAULT_LOCALE, LOCALE_LIST, routing } from '@/i18n/routing';
import { useLocaleStore } from '@/stores/locale-store'; import { useLocaleStore } from '@/stores/locale-store';
import { useLocale } from 'next-intl'; import { Locale, useLocale } from 'next-intl';
import { useParams } from 'next/navigation'; import { useParams } from 'next/navigation';
import { useEffect, useTransition } from 'react'; import { useEffect, useTransition } from 'react';

View File

@ -8,10 +8,10 @@ import {
DropdownMenuTrigger, DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'; } from '@/components/ui/dropdown-menu';
import { useLocalePathname, useLocaleRouter } from '@/i18n/navigation'; import { useLocalePathname, useLocaleRouter } from '@/i18n/navigation';
import { Locale, LOCALE_LIST, routing } from '@/i18n/routing'; import { LOCALE_LIST, routing } from '@/i18n/routing';
import { useLocaleStore } from '@/stores/locale-store'; import { useLocaleStore } from '@/stores/locale-store';
import { Languages } from 'lucide-react'; import { Languages } from 'lucide-react';
import { useLocale, useTranslations } from 'next-intl'; import { Locale, useLocale, useTranslations } from 'next-intl';
import { useParams } from 'next/navigation'; import { useParams } from 'next/navigation';
import { useEffect, useTransition } from 'react'; import { useEffect, useTransition } from 'react';

View File

@ -1,11 +1,8 @@
import deepmerge from 'deepmerge'; import deepmerge from 'deepmerge';
import { routing } from './routing'; import { routing } from './routing';
import { Locale, Messages } from 'next-intl';
import type messages from '../../messages/en.json'; const importLocale = async (locale: Locale): Promise<Messages> => {
export type Messages = typeof messages;
export const importLocale = async (locale: string): Promise<Messages> => {
return (await import(`../../messages/${locale}.json`)).default as Messages; return (await import(`../../messages/${locale}.json`)).default as Messages;
}; };
@ -16,7 +13,7 @@ export const importLocale = async (locale: string): Promise<Messages> => {
* https://next-intl.dev/docs/usage/configuration#messages * https://next-intl.dev/docs/usage/configuration#messages
*/ */
export const getMessagesForLocale = async ( export const getMessagesForLocale = async (
locale: string locale: Locale
): Promise<Messages> => { ): Promise<Messages> => {
const localeMessages = await importLocale(locale); const localeMessages = await importLocale(locale);
if (locale === routing.defaultLocale) { if (locale === routing.defaultLocale) {

View File

@ -5,6 +5,7 @@ import { routing } from './routing';
* Navigation APIs * Navigation APIs
* *
* https://next-intl.dev/docs/routing/navigation * https://next-intl.dev/docs/routing/navigation
* https://github.com/amannn/next-intl/blob/main/examples/example-app-router/src/i18n/navigation.ts
*/ */
export const { export const {
Link: LocaleLink, Link: LocaleLink,

View File

@ -1,3 +1,4 @@
import { hasLocale } from 'next-intl';
import { getRequestConfig } from 'next-intl/server'; import { getRequestConfig } from 'next-intl/server';
import { getMessagesForLocale } from './messages'; import { getMessagesForLocale } from './messages';
import { routing } from './routing'; import { routing } from './routing';
@ -11,15 +12,17 @@ import { routing } from './routing';
* The first component to use internationalization will call the function defined with getRequestConfig. * The first component to use internationalization will call the function defined with getRequestConfig.
* *
* https://next-intl.dev/docs/usage/configuration * https://next-intl.dev/docs/usage/configuration
* https://github.com/amannn/next-intl/blob/main/examples/example-app-router/src/i18n/request.ts
*/ */
export default getRequestConfig(async ({ requestLocale }) => { export default getRequestConfig(async ({ requestLocale }) => {
// This typically corresponds to the `[locale]` segment // This typically corresponds to the `[locale]` segment
let locale = await requestLocale; let requested = await requestLocale;
// Ensure that the incoming `locale` is valid // Ensure that the incoming `locale` is valid
if (!locale || !routing.locales.includes(locale as any)) { // https://next-intl.dev/blog/next-intl-4-0?s#strictly-typed-locale
locale = routing.defaultLocale; const locale = hasLocale(routing.locales, requested)
} ? requested
: routing.defaultLocale;
// https://next-intl.dev/docs/usage/configuration#messages // https://next-intl.dev/docs/usage/configuration#messages
// If you have incomplete messages for a given locale and would like to use messages // If you have incomplete messages for a given locale and would like to use messages

View File

@ -14,6 +14,7 @@ export const LOCALE_COOKIE_NAME = 'NEXT_LOCALE';
* Next.js internationalized routing * Next.js internationalized routing
* *
* https://next-intl.dev/docs/routing * https://next-intl.dev/docs/routing
* https://github.com/amannn/next-intl/blob/main/examples/example-app-router/src/i18n/routing.ts
*/ */
export const routing = defineRouting({ export const routing = defineRouting({
// A list of all locales that are supported // A list of all locales that are supported
@ -46,4 +47,4 @@ export const routing = defineRouting({
}); });
// export type Pathnames = keyof typeof routing.pathnames; // export type Pathnames = keyof typeof routing.pathnames;
export type Locale = (typeof routing.locales)[number]; // export type Locale = (typeof routing.locales)[number];

View File

@ -6,7 +6,7 @@ import { allPages } from 'content-collections';
* @param locale The locale to get the page for * @param locale The locale to get the page for
* @returns The custom page or undefined if not found * @returns The custom page or undefined if not found
*/ */
export async function getCustomPage(type: string, locale: string) { export async function getCustomPage(type: string, locale: Locale) {
// Find page with matching slug and locale // Find page with matching slug and locale
const page = allPages.find( const page = allPages.find(
(page) => page.slugAsParams === `${type}` && page.locale === locale (page) => page.slugAsParams === `${type}` && page.locale === locale

View File

@ -5,7 +5,7 @@ import { allReleases } from 'content-collections';
* @param locale The locale to get releases for * @param locale The locale to get releases for
* @returns An array of releases sorted by date (newest first) * @returns An array of releases sorted by date (newest first)
*/ */
export async function getReleases(locale: string) { export async function getReleases(locale: Locale) {
// Find all published releases with matching locale // Find all published releases with matching locale
const releases = allReleases.filter( const releases = allReleases.filter(
(release) => release.published && release.locale === locale (release) => release.published && release.locale === locale

View File

@ -1,8 +1,9 @@
import { getWebsiteInfo } from '@/config'; import { getWebsiteInfo } from '@/config';
import { Locale, LOCALE_COOKIE_NAME, routing } from '@/i18n/routing'; import { LOCALE_COOKIE_NAME, routing } from '@/i18n/routing';
import { createTranslator } from '@/i18n/translator'; import { createTranslator } from '@/i18n/translator';
import { type ClassValue, clsx } from 'clsx'; import { type ClassValue, clsx } from 'clsx';
import { parse as parseCookies } from 'cookie'; import { parse as parseCookies } from 'cookie';
import { Locale } from 'next-intl';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
@ -19,7 +20,7 @@ export function cn(...inputs: ClassValue[]) {
export function createTitle( export function createTitle(
title: string, title: string,
addSuffix: boolean = true, addSuffix: boolean = true,
locale: string = 'en' locale: Locale = routing.defaultLocale
): string { ): string {
// Create a simple translator function for default values // Create a simple translator function for default values
const t = createTranslator((key: string) => key); const t = createTranslator((key: string) => key);

View File

@ -1,9 +1,9 @@
import type { Messages } from '@/i18n/messages';
import { getMessagesForLocale } from '@/i18n/messages'; import { getMessagesForLocale } from '@/i18n/messages';
import { Locale, routing } from '@/i18n/routing'; import { routing } from '@/i18n/routing';
import { mailTemplates } from '@/mail/emails'; import { mailTemplates } from '@/mail/emails';
import { sendEmail } from '@/mail/provider/resend'; import { sendEmail } from '@/mail/provider/resend';
import { render } from '@react-email/render'; import { render } from '@react-email/render';
import { Locale, Messages } from 'next-intl';
type Template = keyof typeof mailTemplates; type Template = keyof typeof mailTemplates;

View File

@ -1,5 +1,4 @@
import type { Locale } from '@/i18n/routing'; import { Locale, Messages } from 'next-intl';
import type { Messages } from '@/i18n/messages';
export interface EmailParams { export interface EmailParams {
to: string; to: string;

View File

@ -1,7 +1,8 @@
import { MetadataRoute } from 'next'; import { MetadataRoute } from 'next';
import { routing, Locale } from '@/i18n/routing'; import { routing } from '@/i18n/routing';
import { getLocalePathname } from '@/i18n/navigation'; import { getLocalePathname } from '@/i18n/navigation';
import { getBaseUrl } from './lib/urls/get-base-url'; import { getBaseUrl } from './lib/urls/get-base-url';
import { Locale } from 'next-intl';
/** /**
* https://github.com/javayhu/cnblocks/blob/main/app/sitemap.ts * https://github.com/javayhu/cnblocks/blob/main/app/sitemap.ts

View File

@ -1,4 +1,4 @@
import { Locale } from '@/i18n/routing'; import { Locale } from 'next-intl';
import { create } from 'zustand'; import { create } from 'zustand';
interface LocaleState { interface LocaleState {