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

View File

@@ -3,13 +3,13 @@
import { useCallback, useState } from "react";
import { Handle, Position, type NodeProps } from "@xyflow/react";
import { useAction } from "convex/react";
import { useTranslations } from "next-intl";
import { Download, Loader2 } from "lucide-react";
import { api } from "@/convex/_generated/api";
import type { Id } from "@/convex/_generated/dataModel";
import { useDebouncedCallback } from "@/hooks/use-debounced-callback";
import BaseNodeWrapper from "./base-node-wrapper";
import { toast } from "@/lib/toast";
import { msg } from "@/lib/toast-messages";
import { useCanvasSync } from "@/components/canvas/canvas-sync-context";
interface FrameNodeData {
@@ -19,6 +19,7 @@ interface FrameNodeData {
}
export default function FrameNode({ id, data, selected, width, height }: NodeProps) {
const t = useTranslations('toasts');
const nodeData = data as FrameNodeData;
const { queueNodeDataUpdate, status } = useCanvasSync();
const exportFrame = useAction(api.export.exportFrame);
@@ -54,23 +55,23 @@ export default function FrameNode({ id, data, selected, width, height }: NodePro
try {
const result = await exportFrame({ frameNodeId: id as Id<"nodes"> });
const fileLabel = `${label.trim() || "frame"}.png`;
toast.action(msg.export.frameExported.title, {
toast.action(t('export.frameExported'), {
description: fileLabel,
label: msg.export.download,
label: t('export.download'),
onClick: () => {
window.open(result.url, "_blank", "noopener,noreferrer");
},
successLabel: msg.export.downloaded,
successLabel: t('export.downloaded'),
type: "success",
});
} catch (error) {
const m = error instanceof Error ? error.message : "";
if (m.includes("No images found")) {
toast.error(msg.export.frameEmpty.title, msg.export.frameEmpty.desc);
setExportError(msg.export.frameEmpty.desc);
toast.error(t('export.frameEmptyTitle'), t('export.frameEmptyDesc'));
setExportError(t('export.frameEmptyDesc'));
} else {
toast.error(msg.export.exportFailed.title, m || undefined);
setExportError(m || msg.export.exportFailed.title);
toast.error(t('export.exportFailed'), m || undefined);
setExportError(m || t('export.exportFailed'));
}
} finally {
setIsExporting(false);