- {links.map(([label, href]) => (
+ {links.map(([key, href]) => (
- {label}
+ {t(key)}
))}
diff --git a/src/components/admin/apify-ingest-form.tsx b/src/components/admin/apify-ingest-form.tsx
index e4fb281..f24a216 100644
--- a/src/components/admin/apify-ingest-form.tsx
+++ b/src/components/admin/apify-ingest-form.tsx
@@ -2,9 +2,11 @@
import { FormEvent, useState } from "react";
import { useRouter } from "next/navigation";
+import { useT } from "@/lib/i18n";
export function ApifyIngestForm({ configured }: { configured: boolean }) {
const router = useRouter();
+ const t = useT();
const [busy, setBusy] = useState(false);
const [message, setMessage] = useState("");
const [error, setError] = useState(false);
@@ -15,7 +17,7 @@ export function ApifyIngestForm({ configured }: { configured: boolean }) {
const searchTerms = String(form.get("searchTerms") || "").split(",").map((term) => term.trim()).filter(Boolean);
const maxResults = Number(form.get("maxResults"));
const maxCostUsd = Number(form.get("maxCostUsd"));
- if (!window.confirm(`${searchTerms.join(", ")} için en fazla ${maxResults} reklam çekilsin mi? Harcama üst sınırı $${maxCostUsd.toFixed(2)}.`)) return;
+ if (!window.confirm(t("ingest.confirm", { terms: searchTerms.join(", "), max: maxResults, cost: maxCostUsd.toFixed(2) }))) return;
setBusy(true); setMessage("");
try {
const response = await fetch("/api/admin/ingest/apify", {
@@ -23,16 +25,16 @@ export function ApifyIngestForm({ configured }: { configured: boolean }) {
body: JSON.stringify({ searchTerms, country: form.get("country"), adActiveStatus: form.get("adActiveStatus"), mediaType: form.get("mediaType"), maxResults, maxCostUsd, scrapeAdDetails: form.get("scrapeAdDetails") === "on", includeAboutPage: form.get("includeAboutPage") === "on" })
});
const data = await response.json().catch(() => ({}));
- if (!response.ok) throw new Error("Canlı reklam taraması başarısız oldu.");
- setError(false); setMessage(`${data.imported} reklam işlendi, ${data.failed} kayıt atlandı.`); router.refresh();
+ if (!response.ok) throw new Error(t("ingest.scanFailed"));
+ setError(false); setMessage(t("ingest.done", { imported: data.imported, failed: data.failed })); router.refresh();
} catch (caught) {
- setError(true); setMessage(caught instanceof Error ? caught.message : "İşlem başarısız");
+ setError(true); setMessage(caught instanceof Error && caught.message !== t("ingest.scanFailed") ? caught.message : t("ingest.failed"));
} finally { setBusy(false); }
}
return (
-
{existingCount > 0 ? `Daha fazla “${query}” reklamı getir` : `“${query}” için kayıtlı reklam bulunamadı`}
+
{existingCount > 0 ? t("apifyEmpty.bringMore", { query }) : t("apifyEmpty.noneFound", { query })}
{existingCount > 0
- ? `Şu anda ${existingCount} ilgili reklam gösteriliyor. Yeni Meta reklamlarını aynı detaylı filtrelerle canlı olarak tarayabilirsiniz.`
- : "Bu arama önce AdSeeQ veritabanını kontrol eder. Yeni Meta reklamlarını seçtiğiniz ülke ve medya türüyle canlı olarak getirip aynı aramaya ekleyebilirsiniz."}
+ ? t("apifyEmpty.showingExisting", { count: existingCount })
+ : t("apifyEmpty.dbFirst")}
- Getirilecek reklam adedi
+ {t("apifyEmpty.resultCount")}
setMaxResults(Number(event.target.value))}
className="min-w-0 flex-1 rounded-2xl border border-violet-200 bg-white px-4 py-3 font-bold"
>
- {RESULT_OPTIONS.filter((count) => count <= planLimit).map((count) => {count} reklam )}
+ {RESULT_OPTIONS.filter((count) => count <= planLimit).map((count) => {t("apifyEmpty.adsCount", { count })} )}
- {busy ? (waitSeconds >= 8 ? "Videolar hazırlanıyor…" : "Meta taranıyor…") : planLimit === 0 ? "Planı yükselt" : existingCount > 0 ? "Daha fazla getir" : "Getir"}
+ {busy ? (waitSeconds >= 8 ? t("apifyEmpty.preparingVideos") : t("apifyEmpty.scanningMeta")) : planLimit === 0 ? t("apifyEmpty.upgradePlan") : existingCount > 0 ? t("apifyEmpty.bringMoreBtn") : t("apifyEmpty.bring")}
-
Canlı Meta verisi · {country === "ALL" ? "Tüm dünya" : country} · {mediaType === "ALL" ? "Tüm medya" : mediaType} · {matchMode === "EXACT_PHRASE" ? "Tam ifade" : "Tüm kelimeler"} · Plan limiti: {planLimit || "erişim yok"} reklam
- {busy &&
Meta sonuçları ve gerçek video/görseller hazırlanıyor. Bu işlem genellikle 15–60 saniye sürer.
}
+
{t("apifyEmpty.liveMeta")} · {country === "ALL" ? t("apifyEmpty.allWorld") : country} · {mediaType === "ALL" ? t("apifyEmpty.allMedia") : mediaType} · {matchMode === "EXACT_PHRASE" ? t("apifyEmpty.exactPhrase") : t("apifyEmpty.allWords")} · {t("apifyEmpty.planLimit")}: {planLimit || t("apifyEmpty.noAccess")}
+ {busy &&
{t("apifyEmpty.busyNote")}
}
{message &&
{message}
}
);
diff --git a/src/lib/i18n.ts b/src/lib/i18n.ts
index 3328a43..2777e7e 100644
--- a/src/lib/i18n.ts
+++ b/src/lib/i18n.ts
@@ -198,6 +198,147 @@ const DICT: Dict = {
// ─── Admin ───
"admin.title": { tr: "AdSeeQ Admin", en: "AdSeeQ Admin" },
+ "adminUsers.title": { tr: "Kullanıcı Yönetimi", en: "User Management" },
+ "adminUsers.subtitle": { tr: "Yetki, plan ve kredi bakiyelerini tek yerden yönetin.", en: "Manage roles, plans and credit balances in one place." },
+ "adminUsers.searchPlaceholder": { tr: "E-posta veya ad ara", en: "Search by email or name" },
+ "adminUsers.allRoles": { tr: "Tüm yetkiler", en: "All roles" },
+ "adminUsers.filter": { tr: "Filtrele", en: "Filter" },
+ "adminUsers.googleLinked": { tr: "Google bağlı", en: "Google linked" },
+ "adminUsers.emailAccount": { tr: "E-posta hesabı", en: "Email account" },
+ "adminUsers.lastLogin": { tr: "Son giriş", en: "Last login" },
+ "adminUsers.registered": { tr: "Kayıt", en: "Registered" },
+ "adminUsers.plan": { tr: "Plan", en: "Plan" },
+ "adminUsers.none": { tr: "YOK", en: "NONE" },
+ "adminUsers.roleAndPlan": { tr: "Yetki ve plan", en: "Role and plan" },
+ "adminUsers.noMatch": { tr: "Eşleşen kullanıcı bulunamadı.", en: "No matching users found." },
+ "adminPayments.title": { tr: "Ödemeler", en: "Payments" },
+ "adminPayments.subtitle": { tr: "Manuel tahsilat, referans ve ödeme durumlarını yönetin.", en: "Manage manual collections, references and payment statuses." },
+ "adminPayments.addManual": { tr: "Manuel ödeme ekle", en: "Add manual payment" },
+ "adminPayments.atomicNote": { tr: "Ödeme ile verilen kredi atomik olarak bakiyeye eklenir. İade durumunda kredi otomatik düşmez; kullanıcı ekranından gerekçeli işlem yapın.", en: "Credits granted with a payment are added to the balance atomically. On refund, credits are not deducted automatically; process it with a reason from the user screen." },
+ "adminPayments.user": { tr: "Kullanıcı", en: "User" },
+ "adminPayments.amount": { tr: "Tutar", en: "Amount" },
+ "adminPayments.provider": { tr: "Sağlayıcı", en: "Provider" },
+ "adminPayments.credit": { tr: "Kredi", en: "Credit" },
+ "adminPayments.reference": { tr: "Referans", en: "Reference" },
+ "adminPayments.date": { tr: "Tarih", en: "Date" },
+ "adminPayments.status": { tr: "Durum", en: "Status" },
+ "adminPayments.adminLabel": { tr: "Admin", en: "Admin" },
+ "adminPayments.system": { tr: "Sistem", en: "System" },
+ "adminPayments.noRecords": { tr: "Henüz ödeme kaydı yok.", en: "No payment records yet." },
+ "adminLogs.title": { tr: "Log Merkezi", en: "Log Center" },
+ "adminLogs.subtitle": { tr: "Admin audit, kredi hareketleri, veri işleri ve oturum kayıtları.", en: "Admin audit logs, credit transactions, data jobs and session records." },
+ "adminLogs.auditTitle": { tr: "Admin audit logları", en: "Admin audit logs" },
+ "adminLogs.noAudit": { tr: "Audit kaydı yok.", en: "No audit records." },
+ "adminLogs.creditsTitle": { tr: "Kredi hareketleri", en: "Credit transactions" },
+ "adminLogs.user": { tr: "Kullanıcı", en: "User" },
+ "adminLogs.type": { tr: "Tür", en: "Type" },
+ "adminLogs.amount": { tr: "Miktar", en: "Amount" },
+ "adminLogs.balance": { tr: "Bakiye", en: "Balance" },
+ "adminLogs.reason": { tr: "Neden", en: "Reason" },
+ "adminLogs.date": { tr: "Tarih", en: "Date" },
+ "adminLogs.ingestTitle": { tr: "Ingest işleri", en: "Ingest jobs" },
+ "adminLogs.ok": { tr: "başarılı", en: "succeeded" },
+ "adminLogs.failed": { tr: "hatalı", en: "failed" },
+ "adminLogs.exportTitle": { tr: "Export işleri", en: "Export jobs" },
+ "adminLogs.sessionsTitle": { tr: "Son oturumlar", en: "Recent sessions" },
+ "adminLogs.opened": { tr: "Açılış", en: "Opened" },
+ "adminLogs.expires": { tr: "Bitiş", en: "Expires" },
+ "adminData.title": { tr: "Veri İşleri", en: "Data Operations" },
+ "adminData.subtitle": { tr: "Reklam, mağaza ve ingest operasyonları.", en: "Ad, store and ingest operations." },
+ "adminData.ads": { tr: "Reklamlar", en: "Ads" },
+ "adminData.stores": { tr: "Mağazalar", en: "Stores" },
+ "adminData.users": { tr: "Kullanıcılar", en: "Users" },
+ "adminData.metaAdsTitle": { tr: "Meta Ads Library", en: "Meta Ads Library" },
+ "adminData.metaAdsDesc": { tr: "Anahtar kelimeyle gerçek Meta reklamlarını çekin. Her çalışmada maliyet üst sınırı uygulanır ve tekrar eden reklamlar güncellenir.", en: "Pull real Meta ads by keyword. Each run enforces a cost cap and upserts duplicate ads." },
+ "adminData.ingestJobs": { tr: "Ingest İşleri", en: "Ingest Jobs" },
+ "adminData.ok": { tr: "başarılı", en: "succeeded" },
+ "adminData.failed": { tr: "hatalı", en: "failed" },
+ "adminData.noJobs": { tr: "Henüz ingest işi yok.", en: "No ingest jobs yet." },
+ "storeDetail.visitShop": { tr: "Mağazayı Ziyaret Et", en: "Visit Shop" },
+ "storeDetail.monthlyVisits": { tr: "Aylık Ziyaret", en: "Monthly Visits" },
+ "storeDetail.estRevenue": { tr: "Tahmini Gelir", en: "Est Revenue" },
+ "storeDetail.products": { tr: "Ürünler", en: "Products" },
+ "storeDetail.activeAds": { tr: "Aktif Reklam", en: "Active Ads" },
+ "storeDetail.bestSelling": { tr: "En Çok Satan Ürünler", en: "Best-selling Products" },
+ "storeDetail.pixelsStack": { tr: "Pixel & Yazılım", en: "Pixels & Stack" },
+ "storeDetail.topMetaAds": { tr: "Öne Çıkan Meta Reklamları", en: "Top Meta Ads" },
+ "storeDetail.similarStores": { tr: "Benzer Mağazalar", en: "Similar Stores" },
+ "storeDetail.visits": { tr: "ziyaret", en: "visits" },
+ "storeDetail.topPercent": { tr: "Top %{{percentile}}", en: "Top {{percentile}}%" },
+ "storeDetail.daysRunning": { tr: "{{days}} gün", en: "{{days}} days" },
+ "ai.noHook": { tr: "Metin kancası bulunamadı", en: "No text hook found" },
+ "ai.labelArchive": { tr: "ARŞİV", en: "ARCHIVE" },
+ "ai.recArchive": { tr: "Reklam artık aktif değil; mesajı referans olarak sakla, doğrudan ölçekleme yapma.", en: "The ad is no longer active; keep the message as a reference, do not scale directly." },
+ "ai.labelScale": { tr: "ÖLÇEKLE", en: "SCALE" },
+ "ai.recScale": { tr: "{{days}} günlük dayanıklılık güçlü bir pazar uyumuna işaret ediyor. Kancayı yeni {{media}} varyasyonlarında test et.", en: "{{days}} days of durability signals strong market fit. Test the hook in new {{media}} variations." },
+ "ai.labelVariation": { tr: "VARYASYON", en: "VARIATION" },
+ "ai.recVariation": { tr: "Kreatif yeterli süre yaşamış. Aynı vaadi farklı açılış karesi ve CTA ile çoğalt.", en: "The creative has lived long enough. Replicate the same promise with a different opening frame and CTA." },
+ "ai.labelWatch": { tr: "İZLE", en: "WATCH" },
+ "ai.recWatch": { tr: "Reklam yeni. Harcama sinyali oluşmadan önce birkaç gün daha performansını izle.", en: "The ad is new. Watch its performance for a few more days before a spend signal forms." },
+ "adminNav.overview": { tr: "Genel Bakış", en: "Overview" },
+ "adminNav.users": { tr: "Kullanıcılar", en: "Users" },
+ "adminNav.payments": { tr: "Ödemeler", en: "Payments" },
+ "adminNav.logs": { tr: "Log Merkezi", en: "Log Center" },
+ "adminNav.data": { tr: "Veri İşleri", en: "Data Ops" },
+ "common.operationFailed": { tr: "İşlem başarısız", en: "Operation failed" },
+ "common.apply": { tr: "Uygula", en: "Apply" },
+ "common.save": { tr: "Kaydet", en: "Save" },
+ "common.saving": { tr: "Kaydediliyor...", en: "Saving..." },
+ "credit.confirmRemove": { tr: "{{amount}} kredi bakiyeden çıkarılsın mı?", en: "Remove {{amount}} credits from the balance?" },
+ "credit.newBalance": { tr: "Yeni bakiye: {{balance}}", en: "New balance: {{balance}}" },
+ "credit.balance": { tr: "Kredi bakiyesi", en: "Credit balance" },
+ "credit.add": { tr: "Kredi ekle", en: "Add credits" },
+ "credit.remove": { tr: "Kredi çıkar", en: "Remove credits" },
+ "credit.amount": { tr: "Miktar", en: "Amount" },
+ "credit.reason": { tr: "İşlem nedeni", en: "Reason" },
+ "role.confirmDemote": { tr: "Bu kullanıcının admin yetkisi kaldırılsın mı?", en: "Remove admin privileges from this user?" },
+ "role.updated": { tr: "Yetki güncellendi", en: "Role updated" },
+ "role.assign": { tr: "Yetki ata", en: "Assign role" },
+ "role.protected": { tr: "Korumalı admin", en: "Protected admin" },
+ "plan.updated": { tr: "Plan güncellendi", en: "Plan updated" },
+ "plan.save": { tr: "Planı kaydet", en: "Save plan" },
+ "payment.saved": { tr: "Ödeme kaydedildi", en: "Payment saved" },
+ "payment.selectUser": { tr: "Kullanıcı seçin", en: "Select user" },
+ "payment.amount": { tr: "Ödeme tutarı", en: "Amount" },
+ "payment.credit": { tr: "Verilecek kredi", en: "Credits to grant" },
+ "payment.reference": { tr: "Dekont / referans no (opsiyonel)", en: "Receipt / reference no (optional)" },
+ "payment.description": { tr: "Açıklama", en: "Description" },
+ "payment.manualDefault": { tr: "Manuel ödeme", en: "Manual payment" },
+ "payment.save": { tr: "Ödemeyi kaydet", en: "Save payment" },
+ "payment.confirmStatus": { tr: "Ödeme {{status}} yapılsın mı? Kredi bakiyesi otomatik değişmez.", en: "Mark payment as {{status}}? Credit balance does not change automatically." },
+ "apifyEmpty.scanFailed": { tr: "Canlı reklam taraması başarısız oldu.", en: "Live ad scan failed." },
+ "apifyEmpty.successPrepared": { tr: "Meta'da {{received}} aday tarandı, {{relevant}} ilgili reklam bulundu; {{prepared}} reklamın medyası hazırlandı. Sonuçlar yenileniyor…", en: "Scanned {{received}} candidates on Meta, found {{relevant}} relevant ads; media prepared for {{prepared}}. Refreshing results…" },
+ "apifyEmpty.successNoCreative": { tr: "Meta'da {{received}} aday ve {{relevant}} ilgili reklam bulundu; seçili medya türünde kullanılabilir kreatif bulunamadı.", en: "Found {{received}} candidates and {{relevant}} relevant ads on Meta; no creatives available for the selected media type." },
+ "apifyEmpty.bringMore": { tr: "Daha fazla \"{{query}}\" reklamı getir", en: "Bring more \"{{query}}\" ads" },
+ "apifyEmpty.noneFound": { tr: "\"{{query}}\" için kayıtlı reklam bulunamadı", en: "No saved ads found for \"{{query}}\"" },
+ "apifyEmpty.showingExisting": { tr: "Şu anda {{count}} ilgili reklam gösteriliyor. Yeni Meta reklamlarını aynı detaylı filtrelerle canlı olarak tarayabilirsiniz.", en: "Currently showing {{count}} relevant ads. You can scan new Meta ads live with the same detailed filters." },
+ "apifyEmpty.dbFirst": { tr: "Bu arama önce AdSeeQ veritabanını kontrol eder. Yeni Meta reklamlarını seçtiğiniz ülke ve medya türüyle canlı olarak getirip aynı aramaya ekleyebilirsiniz.", en: "This search checks the AdSeeQ database first. You can pull new Meta ads live with your selected country and media type and add them to the same search." },
+ "apifyEmpty.resultCount": { tr: "Getirilecek reklam adedi", en: "Number of ads to fetch" },
+ "apifyEmpty.adsCount": { tr: "{{count}} reklam", en: "{{count}} ads" },
+ "apifyEmpty.preparingVideos": { tr: "Videolar hazırlanıyor…", en: "Preparing videos…" },
+ "apifyEmpty.scanningMeta": { tr: "Meta taranıyor…", en: "Scanning Meta…" },
+ "apifyEmpty.upgradePlan": { tr: "Planı yükselt", en: "Upgrade plan" },
+ "apifyEmpty.bringMoreBtn": { tr: "Daha fazla getir", en: "Bring more" },
+ "apifyEmpty.bring": { tr: "Getir", en: "Fetch" },
+ "apifyEmpty.liveMeta": { tr: "Canlı Meta verisi", en: "Live Meta data" },
+ "apifyEmpty.allWorld": { tr: "Tüm dünya", en: "Worldwide" },
+ "apifyEmpty.allMedia": { tr: "Tüm medya", en: "All media" },
+ "apifyEmpty.exactPhrase": { tr: "Tam ifade", en: "Exact phrase" },
+ "apifyEmpty.allWords": { tr: "Tüm kelimeler", en: "All words" },
+ "apifyEmpty.planLimit": { tr: "Plan limiti", en: "Plan limit" },
+ "apifyEmpty.noAccess": { tr: "erişim yok", en: "no access" },
+ "apifyEmpty.busyNote": { tr: "Meta sonuçları ve gerçek video/görseller hazırlanıyor. Bu işlem genellikle 15–60 saniye sürer.", en: "Preparing Meta results and real videos/images. This usually takes 15–60 seconds." },
+ "ingest.confirm": { tr: "{{terms}} için en fazla {{max}} reklam çekilsin mi? Harcama üst sınırı ${{cost}}.", en: "Pull up to {{max}} ads for {{terms}}? Spend cap ${{cost}}." },
+ "ingest.scanFailed": { tr: "Canlı reklam taraması başarısız oldu.", en: "Live ad scan failed." },
+ "ingest.done": { tr: "{{imported}} reklam işlendi, {{failed}} kayıt atlandı.", en: "{{imported}} ads processed, {{failed}} records skipped." },
+ "ingest.failed": { tr: "İşlem başarısız", en: "Operation failed" },
+ "ingest.termsPlaceholder": { tr: "Anahtar kelimeler: skincare, dog collar", en: "Keywords: skincare, dog collar" },
+ "ingest.scrapeDetails": { tr: "Yaratıcı/CTA detaylarını al", en: "Scrape creative/CTA details" },
+ "ingest.aboutPage": { tr: "Reklamveren detaylarını al", en: "Scrape advertiser details" },
+ "ingest.costLimit": { tr: "Harcama üst sınırı (USD)", en: "Spend cap (USD)" },
+ "ingest.waitingConfig": { tr: "Veri kaynağı ayarı bekleniyor", en: "Waiting for data source config" },
+ "ingest.scanning": { tr: "Veriler taranıyor...", en: "Scanning..." },
+ "ingest.pull": { tr: "Reklamları çek", en: "Pull ads" },
"admin.controlCenter": { tr: "Kontrol Merkezi", en: "Control Center" },
"admin.description": { tr: "Kullanıcı, gelir, kredi ve operasyon görünümü.", en: "User, revenue, credit and operations overview." },
"admin.systemActive": { tr: "Sistem aktif", en: "System active" },
@@ -251,7 +392,6 @@ const DICT: Dict = {
"common.loading": { tr: "Yükleniyor...", en: "Loading..." },
"common.search": { tr: "Ara", en: "Search" },
"common.filter": { tr: "Filtrele", en: "Filter" },
- "common.save": { tr: "Kaydet", en: "Save" },
"common.cancel": { tr: "İptal", en: "Cancel" },
"common.delete": { tr: "Sil", en: "Delete" },
"common.edit": { tr: "Düzenle", en: "Edit" },