Implement internationalization support across components

- 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.
This commit is contained in:
2026-04-01 18:16:52 +02:00
parent 6ce1d4a82e
commit 79d9092d43
44 changed files with 1385 additions and 507 deletions

220
messages/en.json Normal file
View File

@@ -0,0 +1,220 @@
{
"common": {
"save": "Save",
"cancel": "Cancel",
"delete": "Delete",
"close": "Close",
"confirm": "Confirm",
"loading": "Loading…",
"error": "Error",
"success": "Success",
"back": "Back",
"next": "Next",
"upgrade": "Upgrade"
},
"auth": {
"signIn": "Sign in",
"signOut": "Sign out",
"magicLink": "Magic link",
"magicLinkSent": "Magic link sent",
"emailLabel": "Email address",
"emailPlaceholder": "you@email.com"
},
"dashboard": {
"title": "Dashboard",
"newCanvas": "New canvas",
"recentlyOpened": "Recently opened",
"templates": "Templates",
"noCanvases": "No canvases found",
"lastEdited": "Last edited",
"nodeCount": "{count, plural, one {# Node} other {# Nodes}}"
},
"canvas": {
"autosaved": "Autosaved",
"share": "Share",
"credits": "Credits",
"zoomFit": "Fit to window",
"addNode": "Add node",
"sidebar": {
"properties": "Properties",
"layers": "Layer",
"filters": "Filters",
"actions": "Actions"
}
},
"nodes": {
"image": "Image",
"aiImage": "AI image",
"curves": "Curves",
"render": "Render",
"compare": "Compare",
"compareAction": "Compare",
"compareClose": "Close",
"compareCurrent": "Current",
"compareOriginal": "Original",
"compareProcessed": "Processed",
"custom": "Custom",
"customize": "Customize",
"prompts": {
"prompt1": "Prompt 1",
"prompt2": "Prompt 2",
"negative": "Negative",
"style": "Style",
"aspectRatio": "Aspect ratio",
"imageSize": "Image size",
"quality": "Quality",
"seed": "Seed"
},
"status": {
"pending": "Pending",
"processing": "Processing",
"completed": "Completed",
"failed": "Failed"
}
},
"credits": {
"balance": "Balance",
"available": "Available",
"reserved": "Reserved",
"topUp": "Top up",
"upgrade": "Upgrade",
"renewsOn": "Renews on",
"insufficientCredits": "Insufficient credits",
"transactionHistory": "Transaction history"
},
"pricing": {
"free": "Free",
"starter": "Starter",
"pro": "Pro",
"max": "Max",
"perMonth": "/month",
"creditsPerMonth": "Credits per month",
"currentPlan": "Current plan",
"choosePlan": "Choose plan"
},
"errors": {
"generic": "Something went wrong — please try again.",
"networkError": "Connection error — check your internet connection.",
"timeout": "Timeout — credits were not charged.",
"insufficientCredits": "Not enough credits for this operation.",
"rateLimited": "Too many requests — please wait a moment.",
"unauthorized": "Not signed in — please sign in.",
"modelUnavailable": "Model currently unavailable — please choose another.",
"contentPolicy": "Request blocked by content policy.",
"invalidRequest": "Invalid request — please check your input.",
"dailyCap": "Daily limit reached — try again tomorrow.",
"concurrency": "Generation already active — please wait.",
"nodeError": "Node error",
"uploadFailed": "Upload failed",
"creditsTestDisabled": "Test credits are disabled.",
"creditsInvalidAmount": "Invalid amount.",
"creditsBalanceNotFound": "No credit balance found.",
"creditsDailyCapReached": "Daily limit reached — try again tomorrow.",
"creditsConcurrencyLimit": "Generation already active — please wait.",
"openrouterMissingMessage": "OpenRouter: Invalid response.",
"openrouterModelRefusal": "Model refused — {reason}.",
"openrouterNoImageInResponse": "OpenRouter: No image in response.",
"openrouterImageUrlLoadFailed": "OpenRouter: Could not load image URL.",
"openrouterDataUriCreationFailed": "OpenRouter: Could not process image.",
"openrouterDataUriMissingBase64": "OpenRouter: Could not process image."
},
"toasts": {
"canvas": {
"imageUploaded": "Image uploaded",
"uploadFailed": "Upload failed",
"uploadFormatError": "Format \"{format}\" is not supported. Allowed: PNG, JPG, WebP.",
"uploadSizeError": "Maximum file size: {maxMb} MB.",
"nodeRemoved": "Item removed",
"nodesRemoved": "{count, plural, one {Item removed} other {# Items removed}}",
"nodeDeleteBlockedTitle": "Cannot delete right now",
"nodeDeleteBlockedDesc": "Please wait a moment and try again.",
"nodeDeleteOptimisticTitle": "Item is still being created",
"nodeDeleteOptimisticDesc": "This item hasn't finished syncing to the server yet. Once synchronization is complete, you can delete it.",
"nodeDeleteBlockedMultiDesc": "At least one item is still being created. Please wait and try again.",
"nodeDeleteBlockedPartialTitle": "Not all items removed",
"nodeDeleteBlockedPartialSuffixOne": "One item couldn't be deleted; the rest of the selection was removed.",
"nodeDeleteBlockedPartialSuffixOther": "{count} items couldn't be deleted; the rest of the selection was removed."
},
"ai": {
"generating": "Generating image…",
"generated": "Image generated",
"generatedDesc": "{credits} credits used",
"generationQueued": "Generation started",
"generationQueuedDesc": "The image will appear automatically when it's ready.",
"generationFailed": "Generation failed",
"creditsNotCharged": "Credits were not charged",
"insufficientCreditsTitle": "Insufficient credits",
"insufficientCreditsDesc": "{needed} credits needed, {available} available.",
"modelUnavailableTitle": "Model temporarily unavailable",
"modelUnavailableDesc": "Try a different model or try again later.",
"contentPolicyTitle": "Request blocked by content policy",
"contentPolicyDesc": "Try rephrasing your prompt.",
"timeoutTitle": "Generation timed out",
"timeoutDesc": "Credits were not charged.",
"openrouterIssuesTitle": "OpenRouter may be experiencing issues",
"openrouterIssuesDesc": "Several generations have failed.",
"concurrentLimitReachedTitle": "Generation already active",
"concurrentLimitReachedDesc": "Please wait until the current generation completes."
},
"export": {
"frameExported": "Frame exported",
"exportingFrames": "Exporting frames…",
"zipReady": "ZIP ready",
"exportFailed": "Export failed",
"frameEmptyTitle": "Export failed",
"frameEmptyDesc": "Frame has no visible content.",
"noFramesOnCanvasTitle": "Export failed",
"noFramesOnCanvasDesc": "No frames on canvas — create one first.",
"download": "Download",
"downloaded": "Downloaded!"
},
"auth": {
"welcomeBack": "Welcome back",
"welcomeOnDashboard": "Great to have you here",
"checkEmailTitle": "Check your email",
"checkEmailDesc": "Confirmation link sent to {email}.",
"sessionExpiredTitle": "Session expired",
"sessionExpiredDesc": "Please sign in again.",
"signedOut": "Signed out",
"signIn": "Sign in",
"initialSetupTitle": "Starting credits activated",
"initialSetupDesc": "You're ready to go."
},
"billing": {
"subscriptionActivatedTitle": "Subscription activated",
"subscriptionActivatedDesc": "{credits} credits added to your balance.",
"creditsAddedTitle": "Credits added",
"creditsAddedDesc": "+{credits} credits",
"subscriptionCancelledTitle": "Subscription cancelled",
"subscriptionCancelledDesc": "Your credits remain available until {periodEnd}.",
"paymentFailedTitle": "Payment failed",
"paymentFailedDesc": "Please update your payment method.",
"dailyLimitReachedTitle": "Daily limit reached",
"dailyLimitReachedDesc": "Maximum {limit} generations per day in your plan.",
"lowCreditsTitle": "Credits running low",
"lowCreditsDesc": "{remaining} credits remaining.",
"topUp": "Top up",
"upgrade": "Upgrade",
"manage": "Manage",
"redirectingToCheckoutTitle": "Redirecting…",
"redirectingToCheckoutDesc": "You're being redirected to secure checkout.",
"openingPortalTitle": "Opening portal…",
"openingPortalDesc": "You're being redirected to subscription management.",
"testGrantFailedTitle": "Credit grant failed"
},
"system": {
"reconnected": "Connection restored",
"connectionLostTitle": "Connection lost",
"connectionLostDesc": "Changes may not be saved.",
"copiedToClipboard": "Copied to clipboard"
},
"dashboard": {
"renameEmptyTitle": "Invalid name",
"renameEmptyDesc": "Name cannot be empty.",
"renameSuccess": "Workspace renamed",
"renameFailed": "Rename failed",
"deleteSuccess": "Workspace deleted",
"deleteFailed": "Delete failed"
}
}
}