- Integrated `next-intl` for toast messages and locale handling in various components, including `Providers`, `CanvasUserMenu`, and `CreditOverview`. - Replaced hardcoded strings with translation keys to enhance localization capabilities. - Updated `RootLayout` to dynamically set the language attribute based on the user's locale. - Ensured consistent user feedback through localized toast messages in actions such as sign-out, canvas operations, and billing notifications.
208 lines
8.0 KiB
TypeScript
208 lines
8.0 KiB
TypeScript
'use client';
|
|
|
|
import { useTranslations } from 'next-intl';
|
|
import { toast, type ToastDurationOverrides } from './toast';
|
|
import type { CanvasNodeDeleteBlockReason } from './toast';
|
|
|
|
const DURATION = {
|
|
success: 4000,
|
|
successShort: 2000,
|
|
error: 6000,
|
|
warning: 5000,
|
|
info: 4000,
|
|
} as const;
|
|
|
|
type ToastTranslations = ReturnType<typeof useTranslations<'toasts'>>;
|
|
|
|
function canvasNodeDeleteWhy(
|
|
t: ToastTranslations,
|
|
reasons: Set<CanvasNodeDeleteBlockReason>,
|
|
): { title: string; desc: string } {
|
|
if (reasons.size === 0) {
|
|
return {
|
|
title: t('canvas.nodeDeleteBlockedTitle'),
|
|
desc: t('canvas.nodeDeleteBlockedDesc'),
|
|
};
|
|
}
|
|
if (reasons.size === 1) {
|
|
const only = [...reasons][0]!;
|
|
if (only === 'optimistic') {
|
|
return {
|
|
title: t('canvas.nodeDeleteOptimisticTitle'),
|
|
desc: t('canvas.nodeDeleteOptimisticDesc'),
|
|
};
|
|
}
|
|
return {
|
|
title: t('canvas.nodeDeleteBlockedTitle'),
|
|
desc: t('canvas.nodeDeleteBlockedDesc'),
|
|
};
|
|
}
|
|
return {
|
|
title: t('canvas.nodeDeleteBlockedTitle'),
|
|
desc: t('canvas.nodeDeleteBlockedMultiDesc'),
|
|
};
|
|
}
|
|
|
|
export const msg = {
|
|
canvas: {
|
|
imageUploaded: (t: ToastTranslations) => ({
|
|
title: t('canvas.imageUploaded'),
|
|
}),
|
|
uploadFailed: (t: ToastTranslations) => ({
|
|
title: t('canvas.uploadFailed'),
|
|
}),
|
|
uploadFormatError: (t: ToastTranslations, format: string) => ({
|
|
title: t('canvas.uploadFailed'),
|
|
desc: t('canvas.uploadFormatError', { format }),
|
|
}),
|
|
uploadSizeError: (t: ToastTranslations, maxMb: number) => ({
|
|
title: t('canvas.uploadFailed'),
|
|
desc: t('canvas.uploadSizeError', { maxMb }),
|
|
}),
|
|
nodeRemoved: (t: ToastTranslations) => ({
|
|
title: t('canvas.nodeRemoved'),
|
|
}),
|
|
nodesRemoved: (t: ToastTranslations, count: number) => ({
|
|
title: t('canvas.nodesRemoved', { count }),
|
|
}),
|
|
nodeDeleteBlockedExplain: (t: ToastTranslations, reasons: Set<CanvasNodeDeleteBlockReason>) => canvasNodeDeleteWhy(t, reasons),
|
|
nodeDeleteBlockedPartial: (t: ToastTranslations, blockedCount: number, reasons: Set<CanvasNodeDeleteBlockReason>) => {
|
|
const why = canvasNodeDeleteWhy(t, reasons);
|
|
const suffix =
|
|
blockedCount === 1
|
|
? t('canvas.nodeDeleteBlockedPartialSuffixOne')
|
|
: t('canvas.nodeDeleteBlockedPartialSuffixOther', { count: blockedCount });
|
|
return {
|
|
title: t('canvas.nodeDeleteBlockedPartialTitle'),
|
|
desc: `${why.desc} ${suffix}`,
|
|
};
|
|
},
|
|
},
|
|
ai: {
|
|
generating: (t: ToastTranslations) => ({ title: t('ai.generating') }),
|
|
generated: (t: ToastTranslations, credits: number) => ({
|
|
title: t('ai.generated'),
|
|
desc: t('ai.generatedDesc', { credits }),
|
|
}),
|
|
generatedDesc: (t: ToastTranslations, credits: number) => t('ai.generatedDesc', { credits }),
|
|
generationQueued: (t: ToastTranslations) => ({ title: t('ai.generationQueued') }),
|
|
generationQueuedDesc: (t: ToastTranslations) => t('ai.generationQueuedDesc'),
|
|
generationFailed: (t: ToastTranslations) => ({ title: t('ai.generationFailed') }),
|
|
creditsNotCharged: (t: ToastTranslations) => t('ai.creditsNotCharged'),
|
|
insufficientCredits: (t: ToastTranslations, needed: number, available: number) => ({
|
|
title: t('ai.insufficientCreditsTitle'),
|
|
desc: t('ai.insufficientCreditsDesc', { needed, available }),
|
|
}),
|
|
modelUnavailable: (t: ToastTranslations) => ({
|
|
title: t('ai.modelUnavailableTitle'),
|
|
desc: t('ai.modelUnavailableDesc'),
|
|
}),
|
|
contentPolicy: (t: ToastTranslations) => ({
|
|
title: t('ai.contentPolicyTitle'),
|
|
desc: t('ai.contentPolicyDesc'),
|
|
}),
|
|
timeout: (t: ToastTranslations) => ({
|
|
title: t('ai.timeoutTitle'),
|
|
desc: t('ai.timeoutDesc'),
|
|
}),
|
|
openrouterIssues: (t: ToastTranslations) => ({
|
|
title: t('ai.openrouterIssuesTitle'),
|
|
desc: t('ai.openrouterIssuesDesc'),
|
|
}),
|
|
concurrentLimitReached: (t: ToastTranslations) => ({
|
|
title: t('ai.concurrentLimitReachedTitle'),
|
|
desc: t('ai.concurrentLimitReachedDesc'),
|
|
}),
|
|
},
|
|
export: {
|
|
frameExported: (t: ToastTranslations) => ({ title: t('export.frameExported') }),
|
|
exportingFrames: (t: ToastTranslations) => ({ title: t('export.exportingFrames') }),
|
|
zipReady: (t: ToastTranslations) => ({ title: t('export.zipReady') }),
|
|
exportFailed: (t: ToastTranslations) => ({ title: t('export.exportFailed') }),
|
|
frameEmpty: (t: ToastTranslations) => ({
|
|
title: t('export.frameEmptyTitle'),
|
|
desc: t('export.frameEmptyDesc'),
|
|
}),
|
|
noFramesOnCanvas: (t: ToastTranslations) => ({
|
|
title: t('export.noFramesOnCanvasTitle'),
|
|
desc: t('export.noFramesOnCanvasDesc'),
|
|
}),
|
|
download: (t: ToastTranslations) => t('export.download'),
|
|
downloaded: (t: ToastTranslations) => t('export.downloaded'),
|
|
},
|
|
auth: {
|
|
welcomeBack: (t: ToastTranslations) => ({ title: t('auth.welcomeBack') }),
|
|
welcomeOnDashboard: (t: ToastTranslations) => ({ title: t('auth.welcomeOnDashboard') }),
|
|
checkEmail: (t: ToastTranslations, email: string) => ({
|
|
title: t('auth.checkEmailTitle'),
|
|
desc: t('auth.checkEmailDesc', { email }),
|
|
}),
|
|
sessionExpired: (t: ToastTranslations) => ({
|
|
title: t('auth.sessionExpiredTitle'),
|
|
desc: t('auth.sessionExpiredDesc'),
|
|
}),
|
|
signedOut: (t: ToastTranslations) => ({ title: t('auth.signedOut') }),
|
|
signIn: (t: ToastTranslations) => t('auth.signIn'),
|
|
initialSetup: (t: ToastTranslations) => ({
|
|
title: t('auth.initialSetupTitle'),
|
|
desc: t('auth.initialSetupDesc'),
|
|
}),
|
|
},
|
|
billing: {
|
|
subscriptionActivated: (t: ToastTranslations, credits: number) => ({
|
|
title: t('billing.subscriptionActivatedTitle'),
|
|
desc: t('billing.subscriptionActivatedDesc', { credits }),
|
|
}),
|
|
creditsAdded: (t: ToastTranslations, credits: number) => ({
|
|
title: t('billing.creditsAddedTitle'),
|
|
desc: t('billing.creditsAddedDesc', { credits }),
|
|
}),
|
|
subscriptionCancelled: (t: ToastTranslations, periodEnd: string) => ({
|
|
title: t('billing.subscriptionCancelledTitle'),
|
|
desc: t('billing.subscriptionCancelledDesc', { periodEnd }),
|
|
}),
|
|
paymentFailed: (t: ToastTranslations) => ({
|
|
title: t('billing.paymentFailedTitle'),
|
|
desc: t('billing.paymentFailedDesc'),
|
|
}),
|
|
dailyLimitReached: (t: ToastTranslations, limit: number) => ({
|
|
title: t('billing.dailyLimitReachedTitle'),
|
|
desc: t('billing.dailyLimitReachedDesc', { limit }),
|
|
}),
|
|
lowCredits: (t: ToastTranslations, remaining: number) => ({
|
|
title: t('billing.lowCreditsTitle'),
|
|
desc: t('billing.lowCreditsDesc', { remaining }),
|
|
}),
|
|
topUp: (t: ToastTranslations) => t('billing.topUp'),
|
|
upgrade: (t: ToastTranslations) => t('billing.upgrade'),
|
|
manage: (t: ToastTranslations) => t('billing.manage'),
|
|
redirectingToCheckout: (t: ToastTranslations) => ({
|
|
title: t('billing.redirectingToCheckoutTitle'),
|
|
desc: t('billing.redirectingToCheckoutDesc'),
|
|
}),
|
|
openingPortal: (t: ToastTranslations) => ({
|
|
title: t('billing.openingPortalTitle'),
|
|
desc: t('billing.openingPortalDesc'),
|
|
}),
|
|
testGrantFailed: (t: ToastTranslations) => ({ title: t('billing.testGrantFailedTitle') }),
|
|
},
|
|
system: {
|
|
reconnected: (t: ToastTranslations) => ({ title: t('system.reconnected') }),
|
|
connectionLost: (t: ToastTranslations) => ({
|
|
title: t('system.connectionLostTitle'),
|
|
desc: t('system.connectionLostDesc'),
|
|
}),
|
|
copiedToClipboard: (t: ToastTranslations) => ({ title: t('system.copiedToClipboard') }),
|
|
},
|
|
dashboard: {
|
|
renameEmpty: (t: ToastTranslations) => ({
|
|
title: t('dashboard.renameEmptyTitle'),
|
|
desc: t('dashboard.renameEmptyDesc'),
|
|
}),
|
|
renameSuccess: (t: ToastTranslations) => ({ title: t('dashboard.renameSuccess') }),
|
|
renameFailed: (t: ToastTranslations) => ({ title: t('dashboard.renameFailed') }),
|
|
deleteSuccess: (t: ToastTranslations) => ({ title: t('dashboard.deleteSuccess') }),
|
|
deleteFailed: (t: ToastTranslations) => ({ title: t('dashboard.deleteFailed') }),
|
|
},
|
|
} as const;
|