From 542cb66759f8dee25b2d7ef521a4b031c4c39e9a Mon Sep 17 00:00:00 2001 From: poyrazavsever Date: Mon, 20 Jul 2026 11:08:32 +0300 Subject: [PATCH] feat(i18n): update settings and dashboard layout for dynamic translations --- app/(dashboard)/dashboard-client.tsx | 6 +- app/(dashboard)/layout.tsx | 20 +- app/(dashboard)/page.tsx | 14 +- .../languages/[locale]/language-detail.tsx | 2 +- .../[locale]/translations/actions.ts | 34 +++ .../languages/[locale]/translations/page.tsx | 43 ++++ .../translations/translation-editor.tsx | 231 ++++++++++++++++++ .../languages/import-export/actions.ts | 28 +++ .../import-export/import-export-form.tsx | 124 ++++++++++ .../settings/languages/import-export/page.tsx | 5 + .../settings/languages/languages-list.tsx | 19 +- 11 files changed, 496 insertions(+), 30 deletions(-) create mode 100644 app/(dashboard)/settings/languages/[locale]/translations/actions.ts create mode 100644 app/(dashboard)/settings/languages/[locale]/translations/page.tsx create mode 100644 app/(dashboard)/settings/languages/[locale]/translations/translation-editor.tsx create mode 100644 app/(dashboard)/settings/languages/import-export/actions.ts create mode 100644 app/(dashboard)/settings/languages/import-export/import-export-form.tsx create mode 100644 app/(dashboard)/settings/languages/import-export/page.tsx diff --git a/app/(dashboard)/dashboard-client.tsx b/app/(dashboard)/dashboard-client.tsx index 386e542..9bfd7ef 100644 --- a/app/(dashboard)/dashboard-client.tsx +++ b/app/(dashboard)/dashboard-client.tsx @@ -132,7 +132,7 @@ export function DashboardClient({ data }: DashboardClientProps) {
- {entry.name === 'income' ? 'Gelir' : 'Gider'} + {entry.name === 'income' ? t('dashboard.charts.income') : t('dashboard.charts.expense')}
{formatCurrency(Number(entry.value ?? 0))} @@ -247,7 +247,7 @@ export function DashboardClient({ data }: DashboardClientProps) {

- {project.status === 'completed' ? 'Tamamlandı' : project.status === 'active' ? 'Aktif' : 'Beklemede'} + {project.status === 'completed' ? t('dashboard.status.completed') : project.status === 'active' ? t('dashboard.status.active') : t('dashboard.status.pending')}
@@ -274,7 +274,7 @@ export function DashboardClient({ data }: DashboardClientProps) {

{client.name}

-

{client.company_name || "Bireysel"}

+

{client.company_name || t("dashboard.clients.individual")}

{new Date(client.created_at).toLocaleDateString(getDocumentIntlLocale(), { month: "short", day: "numeric" })} diff --git a/app/(dashboard)/layout.tsx b/app/(dashboard)/layout.tsx index 5a19a48..4985443 100644 --- a/app/(dashboard)/layout.tsx +++ b/app/(dashboard)/layout.tsx @@ -19,20 +19,11 @@ export default async function DashboardLayout({ const translator = createTranslator(resolvedLocale.locale, [ "common", "navigation", - "dashboard", - "clients", - "projects", - "tasks", - "calendar", - "finance", - "journal", - "chat", - "settings", "status", "validation", ]); const t = translator.t; - const displayName = profile.displayName || user.name || user.email.split("@")[0] || "Neta Kullanıcısı"; + const displayName = profile.displayName || user.name || user.email.split("@")[0] || t("navigation.account.defaultUser", { fallback: "Neta Kullanıcısı" }); const shortName = displayName @@ -55,15 +46,6 @@ export default async function DashboardLayout({ i18n={getClientI18nPayload(resolvedLocale.locale, [ "common", "navigation", - "dashboard", - "clients", - "projects", - "tasks", - "calendar", - "finance", - "journal", - "chat", - "settings", "status", "validation", ])} diff --git a/app/(dashboard)/page.tsx b/app/(dashboard)/page.tsx index 89fb10d..7e45374 100644 --- a/app/(dashboard)/page.tsx +++ b/app/(dashboard)/page.tsx @@ -1,6 +1,10 @@ import { DashboardClient, type DashboardData } from "./dashboard-client"; import { parseDashboardRange, resolveDashboardRange } from "@/server/services/analytics-range"; import { requireFreelancerBackend } from "@/server/web/freelancer"; +import { requireFreelancer } from "@/server/auth/session"; +import { resolveFreelancerLocale } from "@/server/i18n/resolver"; +import { getClientI18nPayload } from "@/server/i18n/translator"; +import { I18nProvider } from "@/components/i18n/i18n-provider"; export const metadata = { title: "Dashboard" }; @@ -9,6 +13,10 @@ export default async function DashboardPage({ }: { searchParams: Promise<{ [key: string]: string | string[] | undefined }>; }) { + const context = await requireFreelancer(); + const resolvedLocale = await resolveFreelancerLocale(context); + const payload = getClientI18nPayload(resolvedLocale.locale, ["dashboard"]); + const params = await searchParams; const range = parseDashboardRange(params.range); const { actor, service } = await requireFreelancerBackend(); @@ -31,5 +39,9 @@ export default async function DashboardPage({ range, }; - return ; + return ( + + + + ); } diff --git a/app/(dashboard)/settings/languages/[locale]/language-detail.tsx b/app/(dashboard)/settings/languages/[locale]/language-detail.tsx index b692320..bea4b32 100644 --- a/app/(dashboard)/settings/languages/[locale]/language-detail.tsx +++ b/app/(dashboard)/settings/languages/[locale]/language-detail.tsx @@ -123,7 +123,7 @@ export function LanguageDetail({
+
+

+ {locale.nativeName} Çevirileri +

+
+ + +
+ + + + + +
+ + + +
+ {filteredKeys.length === 0 ? ( + + + Gösterilecek çeviri anahtarı bulunamadı. + + + ) : ( + filteredKeys.map((item) => { + const isEdited = edits[item.key] !== undefined; + const currentValue = isEdited ? edits[item.key] : (overrides[item.key] || ""); + + const trVars = item.tr.match(/\{[^}]+\}/g) || []; + const targetVars = currentValue.match(/\{[^}]+\}/g) || []; + const missingVars = trVars.filter(v => !targetVars.includes(v)); + + return ( + + +
+ {item.key} + {isEdited && Değiştirildi} +
+ +
+
+ TR Referans: + {item.tr} +
+
+ EN Referans: + {item.en} +
+
+ +
+ + setEdits(prev => ({ ...prev, [item.key]: e.target.value }))} + className={missingVars.length > 0 ? "border-destructive focus-visible:ring-destructive" : ""} + /> + + {missingVars.length > 0 && ( +

+ + Eksik değişkenler: {missingVars.join(", ")} +

+ )} + + {isEdited && ( +
+ +
+ )} +
+
+
+ ); + }) + )} +
+ + {isDirty && ( +
+ + +
+ )} + + ); +} diff --git a/app/(dashboard)/settings/languages/import-export/actions.ts b/app/(dashboard)/settings/languages/import-export/actions.ts new file mode 100644 index 0000000..2e8eb13 --- /dev/null +++ b/app/(dashboard)/settings/languages/import-export/actions.ts @@ -0,0 +1,28 @@ +"use server"; + +import { revalidatePath } from "next/cache"; +import { getSqliteConnection } from "@/server/db/client"; +import { I18nService } from "@/server/i18n/service"; +import { requireFreelancerBackend } from "@/server/web/freelancer"; + +export async function exportTranslationsAction() { + const { actor } = await requireFreelancerBackend(); + const service = new I18nService(getSqliteConnection().db); + const pkg = service.exportPackage(actor); + return { package: pkg }; +} + +export async function importTranslationsAction(content: string) { + const { actor } = await requireFreelancerBackend(); + const service = new I18nService(getSqliteConnection().db); + + try { + const data = JSON.parse(content); + service.importPackage(actor, data); + revalidatePath("/settings/languages"); + return { success: true }; + } catch (error: any) { + console.error("Import error:", error); + return { errorKey: "common.error.description", details: error.message }; + } +} diff --git a/app/(dashboard)/settings/languages/import-export/import-export-form.tsx b/app/(dashboard)/settings/languages/import-export/import-export-form.tsx new file mode 100644 index 0000000..fc5f32b --- /dev/null +++ b/app/(dashboard)/settings/languages/import-export/import-export-form.tsx @@ -0,0 +1,124 @@ +"use client"; + +import { useState, useTransition } from "react"; +import { Download, Upload, AlertTriangle } from "lucide-react"; +import { Button, Card, CardContent, Label, Input } from "poyraz-ui/atoms"; +import { Alert, AlertDescription, toast } from "poyraz-ui/molecules"; +import { useTranslations } from "@/components/i18n/i18n-provider"; +import { exportTranslationsAction, importTranslationsAction } from "./actions"; + +export function ImportExportForm() { + const t = useTranslations(); + const [pending, startTransition] = useTransition(); + const [file, setFile] = useState(null); + + const handleExport = () => { + startTransition(async () => { + const result = await exportTranslationsAction(); + if (result.package) { + const blob = new Blob([JSON.stringify(result.package, null, 2)], { type: "application/json" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `neta-i18n-export-${new Date().toISOString().split("T")[0]}.json`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + toast.success("Dışa aktarma başarılı."); + } + }); + }; + + const handleImport = () => { + if (!file) return; + + if (!window.confirm("Bu işlem mevcut çevirilerin üzerine yazabilir. Devam etmek istiyor musunuz?")) { + return; + } + + startTransition(async () => { + try { + const text = await file.text(); + const result = await importTranslationsAction(text); + if (result.errorKey) { + toast.error(t(result.errorKey) + (result.details ? ` (${result.details})` : "")); + } else { + toast.success("İçe aktarma başarılı."); + setFile(null); + } + } catch (err) { + toast.error("Dosya okunurken bir hata oluştu."); + } + }); + }; + + return ( +
+
+

İçe / Dışa Aktarma

+

Dil paketlerini ve çevirileri taşıyın veya yedekleyin.

+
+ +
+ + +
+
+ +
+

Dışa Aktar

+
+

+ Mevcut tüm dilleri, aktif çevirileri ve tercih edilen varsayılan dil bilgisini içeren bir JSON yedeği oluşturun. +

+ +
+
+ + + +
+
+ +
+

İçe Aktar

+
+

+ Daha önce Neta üzerinden dışa aktarılmış bir dil paketini içeri yükleyin. +

+ + + + + Aynı dil koduna sahip çevirilerin üzerine yazılacaktır. + + + +
+ + setFile(e.target.files?.[0] || null)} + disabled={pending} + /> +
+ + +
+
+
+
+ ); +} diff --git a/app/(dashboard)/settings/languages/import-export/page.tsx b/app/(dashboard)/settings/languages/import-export/page.tsx new file mode 100644 index 0000000..3f4c852 --- /dev/null +++ b/app/(dashboard)/settings/languages/import-export/page.tsx @@ -0,0 +1,5 @@ +import { ImportExportForm } from "./import-export-form"; + +export default function ImportExportPage() { + return ; +} diff --git a/app/(dashboard)/settings/languages/languages-list.tsx b/app/(dashboard)/settings/languages/languages-list.tsx index 8669fc4..5c8c217 100644 --- a/app/(dashboard)/settings/languages/languages-list.tsx +++ b/app/(dashboard)/settings/languages/languages-list.tsx @@ -72,12 +72,19 @@ export function LanguagesList({ {t("settings.languages.description")}

- +
+ + +