feat(i18n): 为评论系统添加国际化支持

- 在管理后台引入 vue-i18n 并添加多语言文件
- 为前端评论组件添加翻译功能,支持自动语言检测
- 在功能设置中新增管理后台和组件语言配置选项
- 更新 API 接口以支持语言设置存储
- 优化菜单项文本溢出显示样式
This commit is contained in:
anghunk
2026-02-10 14:26:38 +08:00
parent 860830cee2
commit 4d9e2d8d31
39 changed files with 6023 additions and 332 deletions

View File

@@ -11,6 +11,7 @@
"@phosphor-icons/vue": "^2.2.1",
"echarts": "^5.5.0",
"vue": "^3.5.13",
"vue-i18n": "^9.14.5",
"vue-router": "^4.4.5"
},
"devDependencies": {
@@ -20,4 +21,4 @@
"vite": "^6.0.11",
"vue-tsc": "^2.1.10"
}
}
}

41
cwd-admin/src/i18n.ts Normal file
View File

@@ -0,0 +1,41 @@
import { createI18n } from 'vue-i18n';
import zhCN from './locales/zh-CN.json';
import enUS from './locales/en-US.json';
import zhTW from './locales/zh-TW.json';
import es from './locales/es.json';
import pt from './locales/pt.json';
import fr from './locales/fr.json';
import de from './locales/de.json';
import ja from './locales/ja.json';
import ko from './locales/ko.json';
import ru from './locales/ru.json';
import it from './locales/it.json';
import nl from './locales/nl.json';
import ar from './locales/ar.json';
import hi from './locales/hi.json';
import id from './locales/id.json';
const i18n = createI18n({
legacy: false, // 使用 Composition API
locale: localStorage.getItem('admin_language') || 'zh-CN', // 默认语言
fallbackLocale: 'zh-CN',
messages: {
'zh-CN': zhCN,
'en-US': enUS,
'zh-TW': zhTW,
'es': es,
'pt': pt,
'fr': fr,
'de': de,
'ja': ja,
'ko': ko,
'ru': ru,
'it': it,
'nl': nl,
'ar': ar,
'hi': hi,
'id': id,
},
});
export default i18n;

View File

@@ -0,0 +1,304 @@
{
"menu": {
"comments": "إدارة التعليقات",
"stats": "لوحة المعلومات",
"analytics": "إحصائيات الزيارات",
"settings": "إعدادات الموقع",
"data": "إدارة البيانات"
},
"layout": {
"toggleMenu": "تبديل القائمة",
"docs": "الوثائق",
"logout": "تسجيل الخروج",
"moreActions": "المزيد من الإجراءات",
"defaultSite": "الموقع الافتراضي",
"theme": {
"light": "فاتح",
"dark": "داكن",
"system": "النظام"
},
"version": {
"title": "معلومات الإصدار",
"apiAddress": "عنوان API",
"apiVersion": "إصدار الواجهة",
"adminVersion": "إصدار الإدارة",
"match": "إصدار الإدارة الحالي متطابق مع إصدار الواجهة، يمكنك استخدامه بشكل طبيعي.",
"mismatch": "إصدار الإدارة الحالي غير متطابق مع إصدار الواجهة، يوصى بتحديث خدمة API لتجنب مشاكل التوافق.",
"fetchError": "فشل في جلب إصدار الواجهة:",
"loading": "جارٍ التحميل...",
"notConfigured": "غير مكون",
"notFetched": "لم يتم الجلب",
"ok": "حسناً"
}
},
"stats": {
"title": "لوحة المعلومات",
"overview": "نظرة عامة",
"total": "إجمالي التعليقات",
"approved": "تمت الموافقة",
"pending": "قيد الانتظار",
"rejected": "مرفوض",
"trend": "اتجاه التعليقات",
"bySite": "الإحصائيات حسب الموقع",
"last7Days": "آخر 7 أيام",
"last30Days": "آخر 30 يوماً",
"noData": "لا توجد بيانات تعليقات",
"table": {
"domain": "النطاق",
"total": "الإجمالي",
"approved": "تمت الموافقة",
"pending": "قيد الانتظار",
"rejected": "مرفوض"
},
"loadError": "فشل تحميل الإحصائيات"
},
"analytics": {
"title": "إحصائيات الزيارات",
"overview": "نظرة عامة",
"totalPv": "إجمالي الزيارات",
"todayPv": "زيارات اليوم",
"weekPv": "زيارات الأسبوع",
"monthPv": "زيارات الشهر",
"totalPages": "الصفحات التي تمت زيارتها",
"trend": "اتجاه الزيارات",
"pageDetail": "تفاصيل الصفحة",
"likeRank": "أعلى الصفحات إعجاباً",
"last7Days": "آخر 7 أيام",
"last30Days": "آخر 30 يوماً",
"sort": {
"pv": "ترتيب حسب المشاهدات",
"latest": "أحدث زيارة"
},
"table": {
"title": "عنوان الصفحة",
"pv": "المشاهدات",
"time": "آخر زيارة",
"url": "رابط الصفحة",
"rank": "الترتيب",
"like": "الإعجابات"
},
"noData": "لا توجد بيانات زيارة",
"noLikeData": "لا توجد بيانات إعجاب",
"loadError": "فشل تحميل إحصائيات الزيارات"
},
"data": {
"title": "إدارة البيانات",
"sections": {
"comments": {
"title": "بيانات التعليقات",
"desc": "إدارة محتوى التعليقات، يدعم نقل البيانات من أنظمة التعليقات الأخرى.",
"exportLabel": "تصدير:",
"exporting": "جارٍ التصدير...",
"exportJson": "تصدير JSON",
"importLabel": "استيراد:",
"importButton": "استيراد التعليقات",
"source": {
"cwd": "CWD (.json)",
"twikoo": "Twikoo (.json)",
"artalk": "Artalk (.json)"
}
},
"config": {
"title": "تكوين النظام",
"desc": "إدارة إعدادات الخلفية، تكوين البريد الإلكتروني، القوائم السوداء، إلخ.",
"export": "تصدير التكوين",
"import": "استيراد التكوين"
},
"stats": {
"title": "إحصائيات الزيارات",
"desc": "إدارة زيارات المقالات، الإعجابات، واتجاهات الزيارة اليومية.",
"export": "تصدير الإحصائيات",
"import": "استيراد الإحصائيات"
},
"backup": {
"title": "نسخ احتياطي كامل",
"desc": "نسخ احتياطي أو استعادة جميع بيانات النظام (التعليقات + التكوين + الإحصائيات).",
"export": "تصدير كامل",
"import": "استعادة كاملة"
}
},
"logs": {
"title": "سجلات العمليات"
},
"prefixModal": {
"title": "بادئة URL مفقودة",
"descPart1": "تم اكتشاف ",
"descPart2": " تعليق بدون بادئة النطاق (http/https).",
"descPart3": "هل تريد إضافتها أثناء الاستيراد؟",
"label": "بادئة النطاق (مثال: https://example.me)",
"placeholder": "الرجاء إدخال بادئة النطاق",
"skip": "استيراد مباشر (بدون إضافة)",
"confirm": "إضافة واستيراد"
},
"messages": {
"exportSuccess": "تم التصدير بنجاح",
"exportFailed": "فشل التصدير",
"fileReadFailed": "فشل قراءة الملف",
"jsonParseFailed": "فشل تحليل JSON، يرجى التحقق من تنسيق الملف",
"importConfigSuccess": "تم استيراد التكوين بنجاح",
"importStatsSuccess": "تم استيراد بيانات الإحصائيات بنجاح",
"importBackupSuccess": "تمت الاستعادة الكاملة بنجاح",
"importCommentsSuccess": "تم استيراد التعليقات بنجاح",
"prefixRequired": "الرجاء إدخال بادئة النطاق",
"importStart": "بدء الاستيراد: {name} (الوضع: {mode})",
"fileParseSuccess": "تم تحليل الملف بنجاح، جارٍ المعالجة...",
"errorWithMessage": "خطأ: {msg}",
"parsedCommentsCount": "تم تحليل {count} تعليق",
"detectMissingPrefix": "تم اكتشاف {count} رابط بدون بادئة، في انتظار تأكيد المستخدم...",
"prefixAdded": "تمت إضافة البادئة، متابعة الاستيراد...",
"skipPrefix": "تخطى المستخدم إضافة البادئة",
"readFileFailedLog": "فشل قراءة الملف",
"importCommentsDone": "اكتمل الاستيراد: {message}"
}
},
"comments": {
"title": "إدارة التعليقات",
"statusFilter": {
"all": "كل الحالات",
"approved": "تمت الموافقة",
"pending": "قيد الانتظار",
"rejected": "مرفوض"
},
"refresh": "تحديث",
"table": {
"author": "المستخدم",
"content": "المحتوى",
"path": "المسار",
"status": "الحالة",
"actions": "إجراءات",
"admin": "المسؤول",
"time": "الوقت",
"ip": "IP",
"email": "البريد الإلكتروني"
},
"actions": {
"approve": "موافقة",
"pending": "انتظار",
"reject": "رفض",
"edit": "تحرير",
"delete": "حذف",
"blockIp": "حظر IP",
"blockEmail": "حظر البريد الإلكتروني",
"pin": "تثبيت"
},
"pagination": {
"prev": "السابق",
"next": "التالي",
"jumpTo": "انتقال إلى",
"page": "صفحة",
"confirm": "تأكيد"
},
"confirmDelete": "هل أنت متأكد من حذف التعليق {id}؟",
"confirmBlockIp": "هل أنت متأكد من إضافة IP {ip} إلى القائمة السوداء؟",
"confirmBlockEmail": "هل أنت متأكد من إضافة البريد الإلكتروني {email} إلى القائمة السوداء؟",
"successBlockIp": "تمت إضافة IP إلى القائمة السوداء",
"successBlockEmail": "تمت إضافة البريد الإلكتروني إلى القائمة السوداء",
"empty": "لا توجد بيانات",
"editModal": {
"title": "تحرير التعليق",
"name": "اسم الزائر",
"email": "بريد الزائر",
"url": "رابط الزائر",
"postSlug": "معرف الصفحة",
"postUrl": "رابط التعليق",
"content": "محتوى التعليق",
"status": "الحالة",
"priority": "الأولوية (1 افتراضي، الرقم الأكبر يظهر أولاً)",
"cancel": "إلغاء",
"save": "حفظ",
"saving": "جارٍ الحفظ..."
}
},
"settings": {
"title": "إعدادات الموقع",
"tabs": {
"comment": "التعليقات والأمان",
"feature": "مفاتيح الميزات",
"emailNotify": "إشعارات البريد",
"telegramNotify": "إشعارات Telegram",
"display": "العرض"
},
"comment": {
"title": "تكوين عرض التعليقات",
"adminEmail": "بريد المسؤول",
"adminBadge": "نص شارة المسؤول (اتركه فارغاً لاستخدام الرمز الافتراضي)",
"adminEnabled": "تمكين عرض شارة المسؤول",
"avatarPrefix": "بادئة الصورة الرمزية (الافتراضي: https://gravatar.com/avatar)",
"securityTitle": "إعدادات الأمان",
"requireReview": "هل تتطلب التعليقات الجديدة مراجعة قبل العرض",
"adminKey": "مفتاح تعليق المسؤول (مطلوب عند استخدام بريد المسؤول للتعليق)",
"adminKeyPlaceholder": "أدخل المفتاح للإعداد أو التعديل",
"allowedDomains": "النطاقات المسموح بها (اتركه فارغاً للسماح للكل، افصل بفواصل أو مسافات)",
"blockedIps": "القائمة السوداء لـ IP (افصل بفواصل أو أسطر جديدة)",
"blockedEmails": "القائمة السوداء للبريد الإلكتروني (افصل بفواصل أو أسطر جديدة)",
"save": "حفظ",
"saving": "جارٍ الحفظ..."
},
"feature": {
"title": "مفاتيح الميزات",
"articleLike": "تمكين إعجاب المقال",
"articleLikeHint": "عند التمكين، سيظهر زر الإعجاب بالمقال أعلى قسم التعليقات.",
"commentLike": "تمكين إعجاب التعليق",
"commentLikeHint": "عند التمكين، سيظهر زر إعجاب لكل تعليق.",
"imageLightbox": "وضع الصندوق المضيء للصور",
"imageLightboxHint": "عند التمكين، سيؤدي النقر على الصور في التعليقات إلى تكبيرها بملء الشاشة.",
"placeholder": "نص العنصر النائب لمربع التعليق",
"placeholderHint": "نص مخصص لمربع الإدخال. اتركه فارغاً لاستخدام الافتراضي.",
"widgetLanguage": "لغة الأداة",
"widgetLanguageHint": "تعيين لغة أداة التعليقات في الواجهة الأمامية.",
"save": "حفظ",
"saving": "جارٍ الحفظ..."
},
"emailNotify": {
"title": "إعدادات البريد الإلكتروني",
"enable": "تمكين إشعارات البريد",
"smtpTitle": "1. تكوين SMTP",
"provider": "مقدم الخدمة",
"host": "خادم SMTP",
"port": "منفذ SMTP",
"secure": "اتصال SSL آمن",
"user": "حساب البريد الإلكتروني",
"pass": "كلمة المرور/رمز التفويض",
"qqHint": "ملاحظة: لبريد QQ، استخدم رمز التفويض بدلاً من كلمة المرور.",
"163Hint": "ملاحظة: لبريد 163، استخدم رمز التفويض بدلاً من كلمة المرور.",
"templateTitle": "2. قوالب البريد الإلكتروني",
"adminTemplate": "قالب إشعار المسؤول (HTML)",
"adminTemplateHint": "المتغيرات المتاحة: ${commentAuthor}, ${postTitle}, ${postUrl}, ${commentContent}",
"replyTemplate": "قالب إشعار الرد (HTML)",
"replyTemplateHint": "المتغيرات المتاحة: ${toName}, ${replyAuthor}, ${postTitle}, ${postUrl}, ${parentComment}, ${replyContent}",
"testBtn": "إرسال بريد تجريبي",
"testingBtn": "جارٍ الإرسال...",
"resetBtn": "استعادة القوالب الافتراضية",
"save": "حفظ التكوين",
"saving": "جارٍ الحفظ..."
},
"telegramNotify": {
"title": "إعدادات Telegram",
"enable": "تمكين إشعارات Telegram",
"botToken": "رمز البوت (Bot Token)",
"botTokenHint": "ابحث عن @BotFather في Telegram لإنشاء بوت والحصول على الرمز",
"chatId": "معرف المحادثة (Chat ID)",
"chatIdHint": "معرف المستخدم أو المجموعة. أرسل رسالة للبوت واستخدم API أو @userinfobot للعثور عليه.",
"webhookBtn": "إعداد Webhook",
"webhookSetting": "جارٍ الإعداد...",
"testBtn": "إرسال رسالة تجريبية",
"testingBtn": "جارٍ الإرسال...",
"save": "حفظ",
"saving": "جارٍ الحفظ..."
},
"display": {
"title": "إعدادات العرض",
"layoutTitle": "عنوان الخلفية",
"layoutTitleHint": "يظهر في شريط التنقل العلوي.",
"adminLanguage": "لغة الخلفية",
"adminLanguageHint": "تعيين لغة نظام الإدارة.",
"save": "حفظ",
"saving": "جارٍ الحفظ..."
}
},
"common": {
"saveSuccess": "تم الحفظ بنجاح",
"saveFailed": "فشل الحفظ",
"loading": "جارٍ التحميل..."
}
}

View File

@@ -0,0 +1,304 @@
{
"menu": {
"comments": "Kommentarverwaltung",
"stats": "Dashboard",
"analytics": "Statistiken",
"settings": "Einstellungen",
"data": "Datenverwaltung"
},
"layout": {
"toggleMenu": "Menü umschalten",
"docs": "Dokumentation",
"logout": "Abmelden",
"moreActions": "Mehr Aktionen",
"defaultSite": "Standardseite",
"theme": {
"light": "Hell",
"dark": "Dunkel",
"system": "System"
},
"version": {
"title": "Versionsinfo",
"apiAddress": "API-Adresse",
"apiVersion": "API-Version",
"adminVersion": "Admin-Version",
"match": "Admin- und API-Versionen stimmen überein.",
"mismatch": "Versionskonflikt. Bitte aktualisieren Sie den API-Dienst.",
"fetchError": "Versionsabruf fehlgeschlagen:",
"loading": "Laden...",
"notConfigured": "Nicht konfiguriert",
"notFetched": "Nicht abgerufen",
"ok": "OK"
}
},
"stats": {
"title": "Dashboard",
"overview": "Übersicht",
"total": "Gesamtkommentare",
"approved": "Genehmigt",
"pending": "Ausstehend",
"rejected": "Abgelehnt",
"trend": "Kommentartrend",
"bySite": "Nach Seite",
"last7Days": "Letzte 7 Tage",
"last30Days": "Letzte 30 Tage",
"noData": "Keine Daten",
"table": {
"domain": "Domain",
"total": "Gesamt",
"approved": "Genehmigt",
"pending": "Ausstehend",
"rejected": "Abgelehnt"
},
"loadError": "Fehler beim Laden der Statistiken"
},
"analytics": {
"title": "Statistiken",
"overview": "Übersicht",
"totalPv": "Gesamt-PV",
"todayPv": "Heute PV",
"weekPv": "Woche PV",
"monthPv": "Monat PV",
"totalPages": "Besuchte Seiten",
"trend": "Traffic-Trend",
"pageDetail": "Seitendetails",
"likeRank": "Top-Likes",
"last7Days": "Letzte 7 Tage",
"last30Days": "Letzte 30 Tage",
"sort": {
"pv": "Nach PV sortieren",
"latest": "Zuletzt besucht"
},
"table": {
"title": "Seitentitel",
"pv": "PV",
"time": "Letzter Besuch",
"url": "URL",
"rank": "Rang",
"like": "Likes"
},
"noData": "Keine Daten",
"noLikeData": "Keine Like-Daten",
"loadError": "Fehler beim Laden der Analysen"
},
"data": {
"title": "Datenverwaltung",
"sections": {
"comments": {
"title": "Kommentardaten",
"desc": "Kommentare verwalten und importieren.",
"exportLabel": "Export:",
"exporting": "Export läuft...",
"exportJson": "JSON exportieren",
"importLabel": "Import:",
"importButton": "Kommentare importieren",
"source": {
"cwd": "CWD (.json)",
"twikoo": "Twikoo (.json)",
"artalk": "Artalk (.json)"
}
},
"config": {
"title": "Systemkonfig",
"desc": "Einstellungen, E-Mail, Blocklisten, etc.",
"export": "Konfig exportieren",
"import": "Konfig importieren"
},
"stats": {
"title": "Analysedaten",
"desc": "Aufrufe, Likes und Trends.",
"export": "Statistiken exportieren",
"import": "Statistiken importieren"
},
"backup": {
"title": "Vollbackup",
"desc": "Alles sichern oder wiederherstellen (Kommentare + Konfig + Stats).",
"export": "Vollexport",
"import": "Vollwiederherstellung"
}
},
"logs": {
"title": "Betriebsprotokolle"
},
"prefixModal": {
"title": "URL-Präfix fehlt",
"descPart1": "Erkannt: ",
"descPart2": " Kommentare ohne Protokoll-Präfix (http/https).",
"descPart3": "Beim Import hinzufügen?",
"label": "Domain-Präfix (z.B. https://example.me)",
"placeholder": "Präfix eingeben",
"skip": "Überspringen",
"confirm": "Hinzufügen & Importieren"
},
"messages": {
"exportSuccess": "Export erfolgreich",
"exportFailed": "Export fehlgeschlagen",
"fileReadFailed": "Dateilesefehler",
"jsonParseFailed": "JSON-Parse-Fehler",
"importConfigSuccess": "Konfig importiert",
"importStatsSuccess": "Statistiken importiert",
"importBackupSuccess": "Vollwiederherstellung erfolgreich",
"importCommentsSuccess": "Kommentare importiert",
"prefixRequired": "Bitte Präfix eingeben",
"importStart": "Import: {name} (Modus: {mode})",
"fileParseSuccess": "Datei geparst, verarbeite...",
"errorWithMessage": "Fehler: {msg}",
"parsedCommentsCount": "{count} Kommentare geparst",
"detectMissingPrefix": "{count} URLs ohne Präfix, warte auf Bestätigung...",
"prefixAdded": "Präfix hinzugefügt, fahre fort...",
"skipPrefix": "Präfix übersprungen",
"readFileFailedLog": "Dateilesefehler",
"importCommentsDone": "Import fertig: {message}"
}
},
"comments": {
"title": "Kommentarverwaltung",
"statusFilter": {
"all": "Alle",
"approved": "Genehmigt",
"pending": "Ausstehend",
"rejected": "Abgelehnt"
},
"refresh": "Aktualisieren",
"table": {
"author": "Benutzer",
"content": "Kommentar",
"path": "Pfad",
"status": "Status",
"actions": "Aktionen",
"admin": "Admin",
"time": "Zeit",
"ip": "IP",
"email": "E-Mail"
},
"actions": {
"approve": "Genehmigen",
"pending": "Zurückstellen",
"reject": "Ablehnen",
"edit": "Bearbeiten",
"delete": "Löschen",
"blockIp": "IP blockieren",
"blockEmail": "E-Mail blockieren",
"pin": "Anpinnen"
},
"pagination": {
"prev": "Zurück",
"next": "Weiter",
"jumpTo": "Gehe zu",
"page": "Seite",
"confirm": "OK"
},
"confirmDelete": "Kommentar {id} löschen?",
"confirmBlockIp": "IP {ip} blockieren?",
"confirmBlockEmail": "E-Mail {email} blockieren?",
"successBlockIp": "IP blockiert",
"successBlockEmail": "E-Mail blockiert",
"empty": "Keine Daten",
"editModal": {
"title": "Kommentar bearbeiten",
"name": "Name",
"email": "E-Mail",
"url": "URL",
"postSlug": "Slug",
"postUrl": "Post-URL",
"content": "Inhalt",
"status": "Status",
"priority": "Priorität (1=Normal, höher=Oben)",
"cancel": "Abbrechen",
"save": "Speichern",
"saving": "Speichert..."
}
},
"settings": {
"title": "Einstellungen",
"tabs": {
"comment": "Kommentare & Sicherheit",
"feature": "Funktionen",
"emailNotify": "E-Mail-Benachrichtigung",
"telegramNotify": "Telegram-Benachrichtigung",
"display": "Anzeige"
},
"comment": {
"title": "Kommentaranzeige",
"adminEmail": "Admin-E-Mail",
"adminBadge": "Admin-Badge-Text (Leer für Standard-Icon)",
"adminEnabled": "Admin-Badge anzeigen",
"avatarPrefix": "Avatar-Präfix (Standard: https://gravatar.com/avatar)",
"securityTitle": "Sicherheit",
"requireReview": "Überprüfung für neue Kommentare erforderlich",
"adminKey": "Admin-Schlüssel (Erforderlich für Admin-E-Mail-Kommentare)",
"adminKeyPlaceholder": "Schlüssel eingeben",
"allowedDomains": "Erlaubte Domains (Leer für alle. Trennung durch Leerzeichen/Komma)",
"blockedIps": "Blockierte IPs (Trennung durch Komma/Zeilenumbruch)",
"blockedEmails": "Blockierte E-Mails (Trennung durch Komma/Zeilenumbruch)",
"save": "Speichern",
"saving": "Speichert..."
},
"feature": {
"title": "Funktionen",
"articleLike": "Artikel-Likes aktivieren",
"articleLikeHint": "Like-Button oben im Kommentarbereich anzeigen.",
"commentLike": "Kommentar-Likes aktivieren",
"commentLikeHint": "Like-Button bei jedem Kommentar anzeigen.",
"imageLightbox": "Bild-Lightbox",
"imageLightboxHint": "Vollbildvorschau für Bilder in Kommentaren.",
"placeholder": "Platzhaltertext",
"placeholderHint": "Benutzerdefinierter Platzhalter für Kommentareingabe.",
"widgetLanguage": "Widget-Sprache",
"widgetLanguageHint": "Sprache für das Frontend-Widget.",
"save": "Speichern",
"saving": "Speichert..."
},
"emailNotify": {
"title": "E-Mail-Benachrichtigung",
"enable": "E-Mail-Benachrichtigung aktivieren",
"smtpTitle": "1. SMTP-Konfig",
"provider": "Anbieter",
"host": "SMTP-Host",
"port": "SMTP-Port",
"secure": "SSL-Sicherheit",
"user": "E-Mail-Benutzer",
"pass": "Passwort/App-Token",
"qqHint": "Hinweis: Für QQ Mail App-Token verwenden.",
"163Hint": "Hinweis: Für 163 Mail App-Token verwenden.",
"templateTitle": "2. Vorlagen-Konfig",
"adminTemplate": "Admin-Vorlage (HTML)",
"adminTemplateHint": "Vars: ${commentAuthor}, ${postTitle}, ${postUrl}, ${commentContent}",
"replyTemplate": "Antwort-Vorlage (HTML)",
"replyTemplateHint": "Vars: ${toName}, ${replyAuthor}, ${postTitle}, ${postUrl}, ${parentComment}, ${replyContent}",
"testBtn": "Test-E-Mail senden",
"testingBtn": "Sendet...",
"resetBtn": "Vorlagen zurücksetzen",
"save": "Konfig speichern",
"saving": "Speichert..."
},
"telegramNotify": {
"title": "Telegram-Benachrichtigung",
"enable": "Telegram-Benachrichtigung aktivieren",
"botToken": "Bot-Token",
"botTokenHint": "Von @BotFather erhalten",
"chatId": "Chat-ID",
"chatIdHint": "Benutzer-ID oder Gruppen-ID. @userinfobot verwenden.",
"webhookBtn": "Webhook setzen",
"webhookSetting": "Setze...",
"testBtn": "Testnachricht senden",
"testingBtn": "Sendet...",
"save": "Speichern",
"saving": "Speichert..."
},
"display": {
"title": "Anzeigeeinstellungen",
"layoutTitle": "Admin-Titel",
"layoutTitleHint": "Wird in der oberen Navigationsleiste angezeigt.",
"adminLanguage": "Admin-Sprache",
"adminLanguageHint": "Sprache für das Admin-Panel.",
"save": "Speichern",
"saving": "Speichert..."
},
"common": {
"saveSuccess": "Erfolgreich gespeichert",
"saveFailed": "Speichern fehlgeschlagen",
"loading": "Laden..."
}
}
}

View File

@@ -0,0 +1,304 @@
{
"menu": {
"comments": "Comments",
"stats": "Dashboard",
"analytics": "Analytics",
"settings": "Settings",
"data": "Data"
},
"layout": {
"toggleMenu": "Toggle Menu",
"docs": "Documentation",
"logout": "Logout",
"moreActions": "More Actions",
"defaultSite": "Default Site",
"theme": {
"light": "Light",
"dark": "Dark",
"system": "System"
},
"version": {
"title": "Version Info",
"apiAddress": "API Address",
"apiVersion": "API Version",
"adminVersion": "Admin Version",
"match": "Admin and API versions match.",
"mismatch": "Version mismatch. Please update API service to match Admin version.",
"fetchError": "Failed to fetch version:",
"loading": "Loading...",
"notConfigured": "Not Configured",
"notFetched": "Not Fetched",
"ok": "OK"
}
},
"stats": {
"title": "Dashboard",
"overview": "Overview",
"total": "Total Comments",
"approved": "Approved",
"pending": "Pending",
"rejected": "Rejected",
"trend": "Comment Trend",
"bySite": "By Site",
"last7Days": "Last 7 Days",
"last30Days": "Last 30 Days",
"noData": "No Data",
"table": {
"domain": "Domain",
"total": "Total",
"approved": "Approved",
"pending": "Pending",
"rejected": "Rejected"
},
"loadError": "Failed to load stats"
},
"analytics": {
"title": "Analytics",
"overview": "Overview",
"totalPv": "Total PV",
"todayPv": "Today PV",
"weekPv": "Week PV",
"monthPv": "Month PV",
"totalPages": "Pages Visited",
"trend": "Traffic Trend",
"pageDetail": "Page Details",
"likeRank": "Top Liked Pages",
"last7Days": "Last 7 Days",
"last30Days": "Last 30 Days",
"sort": {
"pv": "Sort by PV",
"latest": "Latest Visited"
},
"table": {
"title": "Page Title",
"pv": "PV",
"time": "Last Visit",
"url": "URL",
"rank": "Rank",
"like": "Likes"
},
"noData": "No Data",
"noLikeData": "No Like Data",
"loadError": "Failed to load analytics"
},
"data": {
"title": "Data Management",
"sections": {
"comments": {
"title": "Comments",
"desc": "Manage comments and import from other frameworks.",
"exportLabel": "Export:",
"exporting": "Exporting...",
"exportJson": "Export JSON",
"importLabel": "Import:",
"importButton": "Import Comments",
"source": {
"cwd": "CWD (.json)",
"twikoo": "Twikoo (.json)",
"artalk": "Artalk (.json)"
}
},
"config": {
"title": "System Config",
"desc": "Manage settings, email config, blocklists, etc.",
"export": "Export Config",
"import": "Import Config"
},
"stats": {
"title": "Analytics Data",
"desc": "Manage page views, likes, and trends.",
"export": "Export Stats",
"import": "Import Stats"
},
"backup": {
"title": "Full Backup",
"desc": "Backup or restore all system data (Comments + Config + Stats).",
"export": "Full Export",
"import": "Full Restore"
}
},
"logs": {
"title": "Operation Logs"
},
"prefixModal": {
"title": "Missing URL Prefix Detected",
"descPart1": "Detected ",
"descPart2": " comments with URL missing protocol prefix (http/https).",
"descPart3": "Add prefix during import?",
"label": "Domain Prefix (e.g. https://example.me)",
"placeholder": "Enter domain prefix",
"skip": "Skip (Don't Add)",
"confirm": "Add & Import"
},
"messages": {
"exportSuccess": "Export Success",
"exportFailed": "Export Failed",
"fileReadFailed": "File Read Failed",
"jsonParseFailed": "JSON Parse Failed",
"importConfigSuccess": "Config Imported",
"importStatsSuccess": "Stats Imported",
"importBackupSuccess": "Full Restore Success",
"importCommentsSuccess": "Comments Imported",
"prefixRequired": "Please enter domain prefix",
"importStart": "Importing: {name} (Mode: {mode})",
"fileParseSuccess": "File parsed, processing...",
"errorWithMessage": "Error: {msg}",
"parsedCommentsCount": "Parsed {count} comments",
"detectMissingPrefix": "Detected {count} URLs missing prefix, waiting for confirmation...",
"prefixAdded": "Prefix added, continuing...",
"skipPrefix": "Skipped prefix addition",
"readFileFailedLog": "Read file failed",
"importCommentsDone": "Import Done: {message}"
}
},
"comments": {
"title": "Comment Management",
"statusFilter": {
"all": "All",
"approved": "Approved",
"pending": "Pending",
"rejected": "Rejected"
},
"refresh": "Refresh",
"table": {
"author": "User",
"content": "Comment",
"path": "Path",
"status": "Status",
"actions": "Actions",
"admin": "Admin",
"time": "Time",
"ip": "IP",
"email": "Email"
},
"actions": {
"approve": "Approve",
"pending": "Pending",
"reject": "Reject",
"edit": "Edit",
"delete": "Delete",
"blockIp": "Block IP",
"blockEmail": "Block Email",
"pin": "Pin"
},
"pagination": {
"prev": "Prev",
"next": "Next",
"jumpTo": "Go to",
"page": "Page",
"confirm": "OK"
},
"confirmDelete": "Delete comment {id}?",
"confirmBlockIp": "Block IP {ip}?",
"confirmBlockEmail": "Block Email {email}?",
"successBlockIp": "IP Blocked",
"successBlockEmail": "Email Blocked",
"empty": "No Data",
"editModal": {
"title": "Edit Comment",
"name": "Nickname",
"email": "Email",
"url": "URL",
"postSlug": "Slug",
"postUrl": "Post URL",
"content": "Content",
"status": "Status",
"priority": "Priority (1=Normal, higher=Top)",
"cancel": "Cancel",
"save": "Save",
"saving": "Saving..."
}
},
"settings": {
"title": "Settings",
"tabs": {
"comment": "Comments & Security",
"feature": "Features",
"emailNotify": "Email Notify",
"telegramNotify": "Telegram Notify",
"display": "Display"
},
"comment": {
"title": "Comment Display",
"adminEmail": "Admin Email",
"adminBadge": "Admin Badge Text (Empty for default icon)",
"adminEnabled": "Show Admin Badge",
"avatarPrefix": "Avatar Prefix (Default: https://gravatar.com/avatar)",
"securityTitle": "Security",
"requireReview": "Require Review for New Comments",
"adminKey": "Admin Key (Required for admin email comments)",
"adminKeyPlaceholder": "Enter key to set or change",
"allowedDomains": "Allowed Domains (Empty to allow all. Split by space or comma)",
"blockedIps": "Blocked IPs (Split by comma or newline)",
"blockedEmails": "Blocked Emails (Split by comma or newline)",
"save": "Save",
"saving": "Saving..."
},
"feature": {
"title": "Features",
"articleLike": "Enable Article Likes",
"articleLikeHint": "Show like button at top of comments.",
"commentLike": "Enable Comment Likes",
"commentLikeHint": "Show like button on each comment.",
"imageLightbox": "Image Lightbox",
"imageLightboxHint": "Enable fullscreen preview for images in comments.",
"placeholder": "Placeholder Text",
"placeholderHint": "Custom placeholder for comment input.",
"widgetLanguage": "Widget Language",
"widgetLanguageHint": "Language for the frontend widget.",
"save": "Save",
"saving": "Saving..."
},
"emailNotify": {
"title": "Email Notification",
"enable": "Enable Email Notify",
"smtpTitle": "1. SMTP Config",
"provider": "Provider",
"host": "SMTP Host",
"port": "SMTP Port",
"secure": "SSL Secure",
"user": "Email User",
"pass": "Password/App Token",
"qqHint": "Note: For QQ Mail, use App Token instead of password.",
"163Hint": "Note: For 163 Mail, use App Token instead of password.",
"templateTitle": "2. Template Config",
"adminTemplate": "Admin Template (HTML)",
"adminTemplateHint": "Vars: ${commentAuthor}, ${postTitle}, ${postUrl}, ${commentContent}",
"replyTemplate": "Reply Template (HTML)",
"replyTemplateHint": "Vars: ${toName}, ${replyAuthor}, ${postTitle}, ${postUrl}, ${parentComment}, ${replyContent}",
"testBtn": "Send Test Email",
"testingBtn": "Sending...",
"resetBtn": "Reset Templates",
"save": "Save Config",
"saving": "Saving..."
},
"telegramNotify": {
"title": "Telegram Notification",
"enable": "Enable Telegram Notify",
"botToken": "Bot Token",
"botTokenHint": "Get from @BotFather",
"chatId": "Chat ID",
"chatIdHint": "User ID or Group ID. Use @userinfobot to find.",
"webhookBtn": "Set Webhook",
"webhookSetting": "Setting...",
"testBtn": "Send Test Message",
"testingBtn": "Sending...",
"save": "Save",
"saving": "Saving..."
},
"display": {
"title": "Display Settings",
"layoutTitle": "Admin Title",
"layoutTitleHint": "Displayed in top navigation bar.",
"adminLanguage": "Admin Language",
"adminLanguageHint": "Language for the admin panel.",
"save": "Save",
"saving": "Saving..."
}
},
"common": {
"saveSuccess": "Saved Successfully",
"saveFailed": "Save Failed",
"loading": "Loading..."
}
}

View File

@@ -0,0 +1,304 @@
{
"menu": {
"comments": "Gestión de Comentarios",
"stats": "Panel de Control",
"analytics": "Analítica",
"settings": "Ajustes",
"data": "Datos"
},
"layout": {
"toggleMenu": "Alternar Menú",
"docs": "Documentación",
"logout": "Cerrar Sesión",
"moreActions": "Más Acciones",
"defaultSite": "Sitio Predeterminado",
"theme": {
"light": "Claro",
"dark": "Oscuro",
"system": "Sistema"
},
"version": {
"title": "Info de Versión",
"apiAddress": "Dirección API",
"apiVersion": "Versión API",
"adminVersion": "Versión Admin",
"match": "Las versiones Admin y API coinciden.",
"mismatch": "Versión no coincidente. Actualice el servicio API.",
"fetchError": "Error al obtener versión:",
"loading": "Cargando...",
"notConfigured": "No Configurado",
"notFetched": "No Obtenido",
"ok": "OK"
}
},
"stats": {
"title": "Panel de Control",
"overview": "Resumen",
"total": "Total Comentarios",
"approved": "Aprobados",
"pending": "Pendientes",
"rejected": "Rechazados",
"trend": "Tendencia",
"bySite": "Por Sitio",
"last7Days": "Últimos 7 Días",
"last30Days": "Últimos 30 Días",
"noData": "Sin Datos",
"table": {
"domain": "Dominio",
"total": "Total",
"approved": "Aprobados",
"pending": "Pendientes",
"rejected": "Rechazados"
},
"loadError": "Error al cargar estadísticas"
},
"analytics": {
"title": "Analítica",
"overview": "Resumen",
"totalPv": "PV Total",
"todayPv": "PV Hoy",
"weekPv": "PV Semana",
"monthPv": "PV Mes",
"totalPages": "Páginas Visitadas",
"trend": "Tendencia Tráfico",
"pageDetail": "Detalles Página",
"likeRank": "Top Likes",
"last7Days": "Últimos 7 Días",
"last30Days": "Últimos 30 Días",
"sort": {
"pv": "Ordenar por PV",
"latest": "Última Visita"
},
"table": {
"title": "Título Página",
"pv": "PV",
"time": "Última Visita",
"url": "URL",
"rank": "Rango",
"like": "Me gusta"
},
"noData": "Sin Datos",
"noLikeData": "Sin Datos de Me gusta",
"loadError": "Error al cargar analítica"
},
"data": {
"title": "Gestión de Datos",
"sections": {
"comments": {
"title": "Datos Comentarios",
"desc": "Gestionar e importar comentarios.",
"exportLabel": "Exportar:",
"exporting": "Exportando...",
"exportJson": "Exportar JSON",
"importLabel": "Importar:",
"importButton": "Importar Comentarios",
"source": {
"cwd": "CWD (.json)",
"twikoo": "Twikoo (.json)",
"artalk": "Artalk (.json)"
}
},
"config": {
"title": "Config Sistema",
"desc": "Ajustes, correo, lista negra, etc.",
"export": "Exportar Config",
"import": "Importar Config"
},
"stats": {
"title": "Datos Analíticos",
"desc": "Vistas, likes y tendencias.",
"export": "Exportar Stats",
"import": "Importar Stats"
},
"backup": {
"title": "Copia de Seguridad Completa",
"desc": "Copia o restaura todo (Comentarios + Config + Stats).",
"export": "Exportación Completa",
"import": "Restauración Completa"
}
},
"logs": {
"title": "Registros de Operación"
},
"prefixModal": {
"title": "Prefijo URL Faltante",
"descPart1": "Detectados ",
"descPart2": " comentarios sin prefijo de protocolo (http/https).",
"descPart3": "¿Añadir al importar?",
"label": "Prefijo Dominio (ej. https://example.me)",
"placeholder": "Introducir prefijo",
"skip": "Omitir",
"confirm": "Añadir e Importar"
},
"messages": {
"exportSuccess": "Exportación Exitosa",
"exportFailed": "Fallo Exportación",
"fileReadFailed": "Fallo Lectura Archivo",
"jsonParseFailed": "Fallo Análisis JSON",
"importConfigSuccess": "Config Importada",
"importStatsSuccess": "Stats Importadas",
"importBackupSuccess": "Restauración Completa Exitosa",
"importCommentsSuccess": "Comentarios Importados",
"prefixRequired": "Por favor introduzca prefijo",
"importStart": "Importando: {name} (Modo: {mode})",
"fileParseSuccess": "Archivo analizado, procesando...",
"errorWithMessage": "Error: {msg}",
"parsedCommentsCount": "{count} comentarios analizados",
"detectMissingPrefix": "{count} URLs sin prefijo, esperando confirmación...",
"prefixAdded": "Prefijo añadido, continuando...",
"skipPrefix": "Adición prefijo omitida",
"readFileFailedLog": "Fallo lectura archivo",
"importCommentsDone": "Importación Hecha: {message}"
}
},
"comments": {
"title": "Gestión Comentarios",
"statusFilter": {
"all": "Todos",
"approved": "Aprobados",
"pending": "Pendientes",
"rejected": "Rechazados"
},
"refresh": "Refrescar",
"table": {
"author": "Usuario",
"content": "Comentario",
"path": "Ruta",
"status": "Estado",
"actions": "Acciones",
"admin": "Admin",
"time": "Hora",
"ip": "IP",
"email": "Correo"
},
"actions": {
"approve": "Aprobar",
"pending": "Pendiente",
"reject": "Rechazar",
"edit": "Editar",
"delete": "Eliminar",
"blockIp": "Bloquear IP",
"blockEmail": "Bloquear Correo",
"pin": "Fijar"
},
"pagination": {
"prev": "Ant",
"next": "Sig",
"jumpTo": "Ir a",
"page": "Página",
"confirm": "OK"
},
"confirmDelete": "¿Eliminar comentario {id}?",
"confirmBlockIp": "¿Bloquear IP {ip}?",
"confirmBlockEmail": "¿Bloquear correo {email}?",
"successBlockIp": "IP Bloqueada",
"successBlockEmail": "Correo Bloqueado",
"empty": "Sin Datos",
"editModal": {
"title": "Editar Comentario",
"name": "Nombre",
"email": "Correo",
"url": "URL",
"postSlug": "Slug",
"postUrl": "URL Post",
"content": "Contenido",
"status": "Estado",
"priority": "Prioridad (1=Normal, mayor=Arriba)",
"cancel": "Cancelar",
"save": "Guardar",
"saving": "Guardando..."
}
},
"settings": {
"title": "Ajustes",
"tabs": {
"comment": "Comentarios y Seguridad",
"feature": "Características",
"emailNotify": "Notificación Correo",
"telegramNotify": "Notificación Telegram",
"display": "Visualización"
},
"comment": {
"title": "Visualización Comentarios",
"adminEmail": "Correo Admin",
"adminBadge": "Texto Insignia Admin (Vacío para icono por defecto)",
"adminEnabled": "Mostrar Insignia Admin",
"avatarPrefix": "Prefijo Avatar (Defecto: https://gravatar.com/avatar)",
"securityTitle": "Seguridad",
"requireReview": "Requerir Revisión para Nuevos Comentarios",
"adminKey": "Clave Admin (Requerida para comentarios con correo admin)",
"adminKeyPlaceholder": "Introducir clave",
"allowedDomains": "Dominios Permitidos (Vacío para todos. Separar por espacio/coma)",
"blockedIps": "IPs Bloqueadas (Separar por coma/nueva línea)",
"blockedEmails": "Correos Bloqueados (Separar por coma/nueva línea)",
"save": "Guardar",
"saving": "Guardando..."
},
"feature": {
"title": "Características",
"articleLike": "Habilitar Me gusta Artículo",
"articleLikeHint": "Mostrar botón Me gusta arriba de comentarios.",
"commentLike": "Habilitar Me gusta Comentario",
"commentLikeHint": "Mostrar botón Me gusta en cada comentario.",
"imageLightbox": "Lightbox Imagen",
"imageLightboxHint": "Habilitar vista pantalla completa para imágenes.",
"placeholder": "Texto Marcador",
"placeholderHint": "Marcador personalizado para entrada de comentarios.",
"widgetLanguage": "Idioma Widget",
"widgetLanguageHint": "Idioma para el widget frontend.",
"save": "Guardar",
"saving": "Guardando..."
},
"emailNotify": {
"title": "Notificación Correo",
"enable": "Habilitar Notificación Correo",
"smtpTitle": "1. Config SMTP",
"provider": "Proveedor",
"host": "Host SMTP",
"port": "Puerto SMTP",
"secure": "Seguridad SSL",
"user": "Usuario Correo",
"pass": "Contraseña/Token App",
"qqHint": "Nota: Para QQ Mail, usar Token App.",
"163Hint": "Nota: Para 163 Mail, usar Token App.",
"templateTitle": "2. Config Plantillas",
"adminTemplate": "Plantilla Admin (HTML)",
"adminTemplateHint": "Vars: ${commentAuthor}, ${postTitle}, ${postUrl}, ${commentContent}",
"replyTemplate": "Plantilla Respuesta (HTML)",
"replyTemplateHint": "Vars: ${toName}, ${replyAuthor}, ${postTitle}, ${postUrl}, ${parentComment}, ${replyContent}",
"testBtn": "Enviar Correo Prueba",
"testingBtn": "Enviando...",
"resetBtn": "Restablecer Plantillas",
"save": "Guardar Config",
"saving": "Guardando..."
},
"telegramNotify": {
"title": "Notificación Telegram",
"enable": "Habilitar Notificación Telegram",
"botToken": "Token Bot",
"botTokenHint": "Obtener de @BotFather",
"chatId": "ID Chat",
"chatIdHint": "ID Usuario o Grupo. Usar @userinfobot.",
"webhookBtn": "Establecer Webhook",
"webhookSetting": "Configurando...",
"testBtn": "Enviar Mensaje Prueba",
"testingBtn": "Enviando...",
"save": "Guardar",
"saving": "Guardando..."
},
"display": {
"title": "Ajustes Visualización",
"layoutTitle": "Título Admin",
"layoutTitleHint": "Mostrado en barra navegación superior.",
"adminLanguage": "Idioma Admin",
"adminLanguageHint": "Idioma para el panel de administración.",
"save": "Guardar",
"saving": "Guardando..."
},
"common": {
"saveSuccess": "Guardado Exitosamente",
"saveFailed": "Fallo al Guardar",
"loading": "Cargando..."
}
}
}

View File

@@ -0,0 +1,304 @@
{
"menu": {
"comments": "Gestion des Commentaires",
"stats": "Tableau de Bord",
"analytics": "Analytique",
"settings": "Paramètres",
"data": "Données"
},
"layout": {
"toggleMenu": "Basculer le Menu",
"docs": "Documentation",
"logout": "Déconnexion",
"moreActions": "Plus d'Actions",
"defaultSite": "Site par Défaut",
"theme": {
"light": "Clair",
"dark": "Sombre",
"system": "Système"
},
"version": {
"title": "Infos Version",
"apiAddress": "Adresse API",
"apiVersion": "Version API",
"adminVersion": "Version Admin",
"match": "Les versions Admin et API correspondent.",
"mismatch": "Version incompatible. Veuillez mettre à jour le service API.",
"fetchError": "Échec de récupération de version :",
"loading": "Chargement...",
"notConfigured": "Non Configuré",
"notFetched": "Non Récupéré",
"ok": "OK"
}
},
"stats": {
"title": "Tableau de Bord",
"overview": "Aperçu",
"total": "Total Commentaires",
"approved": "Approuvés",
"pending": "En Attente",
"rejected": "Rejetés",
"trend": "Tendance",
"bySite": "Par Site",
"last7Days": "7 Derniers Jours",
"last30Days": "30 Derniers Jours",
"noData": "Aucune Donnée",
"table": {
"domain": "Domaine",
"total": "Total",
"approved": "Approuvés",
"pending": "En Attente",
"rejected": "Rejetés"
},
"loadError": "Erreur chargement stats"
},
"analytics": {
"title": "Analytique",
"overview": "Aperçu",
"totalPv": "Total PV",
"todayPv": "PV Aujourd'hui",
"weekPv": "PV Semaine",
"monthPv": "PV Mois",
"totalPages": "Pages Visitées",
"trend": "Tendance Trafic",
"pageDetail": "Détails Page",
"likeRank": "Top Likes",
"last7Days": "7 Derniers Jours",
"last30Days": "30 Derniers Jours",
"sort": {
"pv": "Trier par PV",
"latest": "Dernière Visite"
},
"table": {
"title": "Titre Page",
"pv": "PV",
"time": "Dernière Visite",
"url": "URL",
"rank": "Rang",
"like": "J'aime"
},
"noData": "Aucune Donnée",
"noLikeData": "Aucune Donnée de Like",
"loadError": "Erreur chargement analytique"
},
"data": {
"title": "Gestion des Données",
"sections": {
"comments": {
"title": "Données Commentaires",
"desc": "Gérer et importer des commentaires.",
"exportLabel": "Exporter :",
"exporting": "Export en cours...",
"exportJson": "Exporter JSON",
"importLabel": "Importer :",
"importButton": "Importer Commentaires",
"source": {
"cwd": "CWD (.json)",
"twikoo": "Twikoo (.json)",
"artalk": "Artalk (.json)"
}
},
"config": {
"title": "Config Système",
"desc": "Paramètres, e-mail, liste noire, etc.",
"export": "Exporter Config",
"import": "Importer Config"
},
"stats": {
"title": "Données Analytiques",
"desc": "Vues, likes et tendances.",
"export": "Exporter Stats",
"import": "Importer Stats"
},
"backup": {
"title": "Sauvegarde Complète",
"desc": "Sauvegarder ou restaurer tout (Commentaires + Config + Stats).",
"export": "Export Complet",
"import": "Restauration Complète"
}
},
"logs": {
"title": "Journal d'Opérations"
},
"prefixModal": {
"title": "Préfixe URL Manquant",
"descPart1": "Détecté ",
"descPart2": " commentaires sans préfixe (http/https).",
"descPart3": "Ajouter lors de l'import ?",
"label": "Préfixe Domaine (ex: https://example.me)",
"placeholder": "Entrer le préfixe",
"skip": "Ignorer",
"confirm": "Ajouter & Importer"
},
"messages": {
"exportSuccess": "Export Réussi",
"exportFailed": "Échec Export",
"fileReadFailed": "Échec Lecture Fichier",
"jsonParseFailed": "Échec Analyse JSON",
"importConfigSuccess": "Config Importée",
"importStatsSuccess": "Stats Importées",
"importBackupSuccess": "Restauration Complète Réussie",
"importCommentsSuccess": "Commentaires Importés",
"prefixRequired": "Veuillez entrer le préfixe",
"importStart": "Import : {name} (Mode : {mode})",
"fileParseSuccess": "Fichier analysé, traitement...",
"errorWithMessage": "Erreur : {msg}",
"parsedCommentsCount": "{count} commentaires analysés",
"detectMissingPrefix": "{count} URL sans préfixe, en attente...",
"prefixAdded": "Préfixe ajouté, continuation...",
"skipPrefix": "Ajout préfixe ignoré",
"readFileFailedLog": "Échec lecture fichier",
"importCommentsDone": "Import Terminé : {message}"
}
},
"comments": {
"title": "Gestion Commentaires",
"statusFilter": {
"all": "Tous",
"approved": "Approuvés",
"pending": "En Attente",
"rejected": "Rejetés"
},
"refresh": "Actualiser",
"table": {
"author": "Utilisateur",
"content": "Commentaire",
"path": "Chemin",
"status": "Statut",
"actions": "Actions",
"admin": "Admin",
"time": "Date",
"ip": "IP",
"email": "E-mail"
},
"actions": {
"approve": "Approuver",
"pending": "Mettre en Attente",
"reject": "Rejeter",
"edit": "Éditer",
"delete": "Supprimer",
"blockIp": "Bloquer IP",
"blockEmail": "Bloquer E-mail",
"pin": "Épingler"
},
"pagination": {
"prev": "Préc",
"next": "Suiv",
"jumpTo": "Aller à",
"page": "Page",
"confirm": "OK"
},
"confirmDelete": "Supprimer le commentaire {id} ?",
"confirmBlockIp": "Bloquer l'IP {ip} ?",
"confirmBlockEmail": "Bloquer l'e-mail {email} ?",
"successBlockIp": "IP Bloquée",
"successBlockEmail": "E-mail Bloqué",
"empty": "Aucune Donnée",
"editModal": {
"title": "Éditer Commentaire",
"name": "Pseudo",
"email": "E-mail",
"url": "URL",
"postSlug": "Slug",
"postUrl": "URL Article",
"content": "Contenu",
"status": "Statut",
"priority": "Priorité (1=Normal, plus élevé=Haut)",
"cancel": "Annuler",
"save": "Sauvegarder",
"saving": "Sauvegarde..."
}
},
"settings": {
"title": "Paramètres",
"tabs": {
"comment": "Commentaires & Sécurité",
"feature": "Fonctionnalités",
"emailNotify": "Notifications E-mail",
"telegramNotify": "Notifications Telegram",
"display": "Affichage"
},
"comment": {
"title": "Affichage Commentaires",
"adminEmail": "E-mail Admin",
"adminBadge": "Badge Admin (Vide pour icône par défaut)",
"adminEnabled": "Afficher Badge Admin",
"avatarPrefix": "Préfixe Avatar (Défaut : https://gravatar.com/avatar)",
"securityTitle": "Sécurité",
"requireReview": "Approbation Requise pour Nouveaux Commentaires",
"adminKey": "Clé Admin (Requise pour commentaires via e-mail admin)",
"adminKeyPlaceholder": "Entrer clé",
"allowedDomains": "Domaines Autorisés (Vide pour tous. Séparer par espace/virgule)",
"blockedIps": "IP Bloquées (Séparer par virgule/nouvelle ligne)",
"blockedEmails": "E-mails Bloqués (Séparer par virgule/nouvelle ligne)",
"save": "Sauvegarder",
"saving": "Sauvegarde..."
},
"feature": {
"title": "Fonctionnalités",
"articleLike": "Activer J'aime Article",
"articleLikeHint": "Afficher bouton J'aime en haut des commentaires.",
"commentLike": "Activer J'aime Commentaire",
"commentLikeHint": "Afficher bouton J'aime sur chaque commentaire.",
"imageLightbox": "Lightbox Image",
"imageLightboxHint": "Aperçu plein écran des images.",
"placeholder": "Texte Placeholder",
"placeholderHint": "Texte d'invite pour la saisie de commentaire.",
"widgetLanguage": "Langue Widget",
"widgetLanguageHint": "Langue pour le widget frontend.",
"save": "Sauvegarder",
"saving": "Sauvegarde..."
},
"emailNotify": {
"title": "Notification E-mail",
"enable": "Activer Notifications E-mail",
"smtpTitle": "1. Config SMTP",
"provider": "Fournisseur",
"host": "Hôte SMTP",
"port": "Port SMTP",
"secure": "Sécurité SSL",
"user": "Utilisateur",
"pass": "Mot de passe/Token",
"qqHint": "Note : Pour QQ Mail, utilisez un Token d'App.",
"163Hint": "Note : Pour 163 Mail, utilisez un Token d'App.",
"templateTitle": "2. Config Modèles",
"adminTemplate": "Modèle Admin (HTML)",
"adminTemplateHint": "Vars : ${commentAuthor}, ${postTitle}, ${postUrl}, ${commentContent}",
"replyTemplate": "Modèle Réponse (HTML)",
"replyTemplateHint": "Vars : ${toName}, ${replyAuthor}, ${postTitle}, ${postUrl}, ${parentComment}, ${replyContent}",
"testBtn": "Envoyer Test",
"testingBtn": "Envoi...",
"resetBtn": "Réinitialiser Modèles",
"save": "Sauvegarder Config",
"saving": "Sauvegarde..."
},
"telegramNotify": {
"title": "Notification Telegram",
"enable": "Activer Notifications Telegram",
"botToken": "Token Bot",
"botTokenHint": "Obtenir via @BotFather",
"chatId": "ID Chat",
"chatIdHint": "ID Utilisateur ou Groupe. Utiliser @userinfobot.",
"webhookBtn": "Définir Webhook",
"webhookSetting": "Configuration...",
"testBtn": "Envoyer Message Test",
"testingBtn": "Envoi...",
"save": "Sauvegarder",
"saving": "Sauvegarde..."
},
"display": {
"title": "Paramètres Affichage",
"layoutTitle": "Titre Admin",
"layoutTitleHint": "Affiché dans la barre de navigation.",
"adminLanguage": "Langue Admin",
"adminLanguageHint": "Langue du panneau d'administration.",
"save": "Sauvegarder",
"saving": "Sauvegarde..."
},
"common": {
"saveSuccess": "Sauvegarde Réussie",
"saveFailed": "Échec Sauvegarde",
"loading": "Chargement..."
}
}
}

View File

@@ -0,0 +1,304 @@
{
"menu": {
"comments": "टिप्पणी प्रबंधन",
"stats": "डेटा डैशबोर्ड",
"analytics": "विज़िट विश्लेषण",
"settings": "साइट सेटिंग्स",
"data": "डेटा प्रबंधन"
},
"layout": {
"toggleMenu": "मेनू टॉगल करें",
"docs": "दस्तावेज़ीकरण",
"logout": "लॉग आउट",
"moreActions": "अधिक क्रियाएं",
"defaultSite": "डिफ़ॉल्ट साइट",
"theme": {
"light": "लाइट",
"dark": "डार्क",
"system": "सिस्टम"
},
"version": {
"title": "संस्करण जानकारी",
"apiAddress": "API पता",
"apiVersion": "इंटरफ़ेस संस्करण",
"adminVersion": "बैकएंड संस्करण",
"match": "वर्तमान बैकएंड संस्करण इंटरफ़ेस संस्करण से मेल खाता है, आप इसे सामान्य रूप से उपयोग कर सकते हैं।",
"mismatch": "वर्तमान बैकएंड संस्करण इंटरफ़ेस संस्करण से मेल नहीं खाता है। संगतता समस्याओं से बचने के लिए API सेवा को अपडेट करने की अनुशंसा की जाती है।",
"fetchError": "इंटरफ़ेस संस्करण प्राप्त करने में विफल:",
"loading": "लोड हो रहा है...",
"notConfigured": "कॉन्फ़िगर नहीं किया गया",
"notFetched": "प्राप्त नहीं किया गया",
"ok": "समझ गया"
}
},
"stats": {
"title": "डेटा डैशबोर्ड",
"overview": "अवलोकन",
"total": "कुल टिप्पणियाँ",
"approved": "स्वीकृत",
"pending": "लंबित",
"rejected": "अस्वीकृत",
"trend": "टिप्पणी रुझान",
"bySite": "साइट द्वारा आँकड़े",
"last7Days": "पिछले 7 दिन",
"last30Days": "पिछले 30 दिन",
"noData": "कोई टिप्पणी डेटा नहीं",
"table": {
"domain": "डोमेन",
"total": "कुल",
"approved": "स्वीकृत",
"pending": "लंबित",
"rejected": "अस्वीकृत"
},
"loadError": "आँकड़े लोड करने में विफल"
},
"analytics": {
"title": "विज़िट विश्लेषण",
"overview": "अवलोकन",
"totalPv": "कुल विज़िट",
"todayPv": "आज की विज़िट",
"weekPv": "इस सप्ताह की विज़िट",
"monthPv": "इस महीने की विज़िट",
"totalPages": "विज़िट किए गए पेज",
"trend": "विज़िट रुझान",
"pageDetail": "पेज विवरण",
"likeRank": "लाइक रैंकिंग",
"last7Days": "पिछले 7 दिन",
"last30Days": "पिछले 30 दिन",
"sort": {
"pv": "PV द्वारा क्रमबद्ध करें",
"latest": "नवीनतम विज़िट"
},
"table": {
"title": "पेज शीर्षक",
"pv": "विज़िट",
"time": "अंतिम विज़िट",
"url": "पेज URL",
"rank": "रैंक",
"like": "लाइक"
},
"noData": "कोई विज़िट डेटा नहीं",
"noLikeData": "कोई लाइक डेटा नहीं",
"loadError": "विज़िट आँकड़े लोड करने में विफल"
},
"data": {
"title": "डेटा प्रबंधन",
"sections": {
"comments": {
"title": "टिप्पणी डेटा",
"desc": "टिप्पणी सामग्री प्रबंधित करें, अन्य टिप्पणी प्रणालियों से माइग्रेशन का समर्थन करें।",
"exportLabel": "निर्यात:",
"exporting": "निर्यात हो रहा है...",
"exportJson": "JSON निर्यात करें",
"importLabel": "आयात:",
"importButton": "टिप्पणियाँ आयात करें",
"source": {
"cwd": "CWD (.json)",
"twikoo": "Twikoo (.json)",
"artalk": "Artalk (.json)"
}
},
"config": {
"title": "सिस्टम कॉन्फ़िगरेशन",
"desc": "बैकएंड सेटिंग्स, ईमेल कॉन्फ़िगरेशन, ब्लैकलिस्ट आदि प्रबंधित करें।",
"export": "कॉन्फ़िगरेशन निर्यात करें",
"import": "कॉन्फ़िगरेशन आयात करें"
},
"stats": {
"title": "विज़िट आँकड़े",
"desc": "लेख विज़िट, लाइक और दैनिक रुझान प्रबंधित करें।",
"export": "आँकड़े निर्यात करें",
"import": "आँकड़े आयात करें"
},
"backup": {
"title": "पूर्ण बैकअप",
"desc": "सभी सिस्टम डेटा (टिप्पणियाँ + कॉन्फ़िगरेशन + आँकड़े) का बैकअप लें या पुनर्स्थापित करें।",
"export": "पूर्ण निर्यात",
"import": "पूर्ण पुनर्स्थापना"
}
},
"logs": {
"title": "ऑपरेशन लॉग"
},
"prefixModal": {
"title": "URL उपसर्ग गायब है",
"descPart1": "पता चला ",
"descPart2": " डोमेन उपसर्ग (http/https) के बिना टिप्पणियाँ।",
"descPart3": "क्या आप आयात करते समय इसे जोड़ना चाहते हैं?",
"label": "डोमेन उपसर्ग (उदाहरण: https://example.me)",
"placeholder": "डोमेन उपसर्ग दर्ज करें",
"skip": "सीधे आयात करें (बिना जोड़े)",
"confirm": "जोड़ें और आयात करें"
},
"messages": {
"exportSuccess": "निर्यात सफल",
"exportFailed": "निर्यात विफल",
"fileReadFailed": "फ़ाइल पढ़ने में विफल",
"jsonParseFailed": "JSON पार्सिंग विफल, कृपया फ़ाइल प्रारूप की जाँच करें",
"importConfigSuccess": "कॉन्फ़िगरेशन आयात सफल",
"importStatsSuccess": "आँकड़े आयात सफल",
"importBackupSuccess": "पूर्ण पुनर्स्थापना सफल",
"importCommentsSuccess": "टिप्पणी आयात सफल",
"prefixRequired": "कृपया डोमेन उपसर्ग दर्ज करें",
"importStart": "आयात शुरू: {name} (मोड: {mode})",
"fileParseSuccess": "फ़ाइल पार्स सफल, प्रसंस्करण...",
"errorWithMessage": "त्रुटि: {msg}",
"parsedCommentsCount": "{count} टिप्पणी डेटा पार्स किया गया",
"detectMissingPrefix": "{count} URL बिना उपसर्ग के पाए गए, उपयोगकर्ता की पुष्टि की प्रतीक्षा में...",
"prefixAdded": "उपसर्ग जोड़ा गया, आयात जारी...",
"skipPrefix": "उपयोगकर्ता ने उपसर्ग जोड़ना छोड़ दिया",
"readFileFailedLog": "फ़ाइल पढ़ने में विफल",
"importCommentsDone": "आयात पूर्ण: {message}"
}
},
"comments": {
"title": "टिप्पणी प्रबंधन",
"statusFilter": {
"all": "सभी स्थितियाँ",
"approved": "स्वीकृत",
"pending": "लंबित",
"rejected": "अस्वीकृत"
},
"refresh": "ताज़ा करें",
"table": {
"author": "उपयोगकर्ता",
"content": "सामग्री",
"path": "पथ",
"status": "स्थिति",
"actions": "क्रियाएं",
"admin": "व्यवस्थापक",
"time": "समय",
"ip": "IP",
"email": "ईमेल"
},
"actions": {
"approve": "स्वीकृत करें",
"pending": "लंबित",
"reject": "अस्वीकार करें",
"edit": "संपादित करें",
"delete": "हटाएं",
"blockIp": "IP ब्लॉक करें",
"blockEmail": "ईमेल ब्लॉक करें",
"pin": "पिन करें"
},
"pagination": {
"prev": "पिछला",
"next": "अगला",
"jumpTo": "पर जाएं",
"page": "पेज",
"confirm": "पुष्टि करें"
},
"confirmDelete": "क्या आप वाकई टिप्पणी {id} हटाना चाहते हैं?",
"confirmBlockIp": "क्या आप वाकई IP {ip} को ब्लैकलिस्ट करना चाहते हैं?",
"confirmBlockEmail": "क्या आप वाकई ईमेल {email} को ब्लैकलिस्ट करना चाहते हैं?",
"successBlockIp": "IP को ब्लैकलिस्ट में जोड़ा गया",
"successBlockEmail": "ईमेल को ब्लैकलिस्ट में जोड़ा गया",
"empty": "कोई डेटा नहीं",
"editModal": {
"title": "टिप्पणी संपादित करें",
"name": "आगंतुक का नाम",
"email": "आगंतुक का ईमेल",
"url": "आगंतुक का URL",
"postSlug": "पेज ID",
"postUrl": "टिप्पणी URL",
"content": "सामग्री",
"status": "स्थिति",
"priority": "प्राथमिकता (1 डिफ़ॉल्ट है, उच्चतर पहले)",
"cancel": "रद्द करें",
"save": "सहेजें",
"saving": "सहेजा जा रहा है..."
}
},
"settings": {
"title": "साइट सेटिंग्स",
"tabs": {
"comment": "टिप्पणियाँ और सुरक्षा",
"feature": "सुविधाएँ",
"emailNotify": "ईमेल सूचनाएँ",
"telegramNotify": "Telegram सूचनाएँ",
"display": "प्रदर्शन"
},
"comment": {
"title": "टिप्पणी प्रदर्शन कॉन्फ़िगरेशन",
"adminEmail": "व्यवस्थापक ईमेल",
"adminBadge": "व्यवस्थापक बैज टेक्स्ट (डिफ़ॉल्ट आइकन के लिए खाली छोड़ें)",
"adminEnabled": "व्यवस्थापक बैज प्रदर्शन सक्षम करें",
"avatarPrefix": "अवतार उपसर्ग (डिफ़ॉल्ट: https://gravatar.com/avatar)",
"securityTitle": "सुरक्षा सेटिंग्स",
"requireReview": "नई टिप्पणियों को प्रदर्शन से पहले समीक्षा की आवश्यकता है",
"adminKey": "व्यवस्थापक टिप्पणी कुंजी (व्यवस्थापक ईमेल का उपयोग करते समय आवश्यक)",
"adminKeyPlaceholder": "सेट या संशोधित करने के लिए कुंजी दर्ज करें",
"allowedDomains": "अनुमत डोमेन (सभी के लिए खाली छोड़ें, अल्पविराम या स्थान से अलग करें)",
"blockedIps": "IP ब्लैकलिस्ट (अल्पविराम या नई लाइन से अलग करें)",
"blockedEmails": "ईमेल ब्लैकलिस्ट (अल्पविराम या नई लाइन से अलग करें)",
"save": "सहेजें",
"saving": "सहेजा जा रहा है..."
},
"feature": {
"title": "सुविधाएँ",
"articleLike": "लेख लाइक सक्षम करें",
"articleLikeHint": "सक्षम होने पर, टिप्पणी अनुभाग के ऊपर एक लेख लाइक बटन प्रदर्शित होगा।",
"commentLike": "टिप्पणी लाइक सक्षम करें",
"commentLikeHint": "सक्षम होने पर, प्रत्येक टिप्पणी के साथ एक लाइक बटन प्रदर्शित होगा।",
"imageLightbox": "छवि लाइटबॉक्स",
"imageLightboxHint": "सक्षम होने पर, टिप्पणियों में छवियों पर क्लिक करने से वे पूर्ण स्क्रीन में दिखाई देंगी।",
"placeholder": "टिप्पणी बॉक्स प्लेसहोल्डर टेक्स्ट",
"placeholderHint": "इनपुट बॉक्स के लिए कस्टम टेक्स्ट। डिफ़ॉल्ट के लिए खाली छोड़ें।",
"widgetLanguage": "विजेट भाषा",
"widgetLanguageHint": "फ्रंटएंड टिप्पणी विजेट की भाषा सेट करें।",
"save": "सहेजें",
"saving": "सहेजा जा रहा है..."
},
"emailNotify": {
"title": "ईमेल सेटिंग्स",
"enable": "ईमेल सूचनाएँ सक्षम करें",
"smtpTitle": "1. SMTP कॉन्फ़िगरेशन",
"provider": "प्रदाता",
"host": "SMTP होस्ट",
"port": "SMTP पोर्ट",
"secure": "SSL कनेक्शन",
"user": "ईमेल खाता",
"pass": "पासवर्ड/ऐप पासवर्ड",
"qqHint": "नोट: QQ मेल के लिए, पासवर्ड के बजाय प्राधिकरण कोड का उपयोग करें।",
"163Hint": "नोट: 163 मेल के लिए, पासवर्ड के बजाय प्राधिकरण कोड का उपयोग करें।",
"templateTitle": "2. ईमेल टेम्प्लेट",
"adminTemplate": "व्यवस्थापक अधिसूचना (HTML)",
"adminTemplateHint": "चर: ${commentAuthor}, ${postTitle}, ${postUrl}, ${commentContent}",
"replyTemplate": "उत्तर अधिसूचना (HTML)",
"replyTemplateHint": "चर: ${toName}, ${replyAuthor}, ${postTitle}, ${postUrl}, ${parentComment}, ${replyContent}",
"testBtn": "परीक्षण ईमेल भेजें",
"testingBtn": "भेजा जा रहा है...",
"resetBtn": "टेम्प्लेट रीसेट करें",
"save": "कॉन्फ़िगरेशन सहेजें",
"saving": "सहेजा जा रहा है..."
},
"telegramNotify": {
"title": "Telegram सेटिंग्स",
"enable": "Telegram सूचनाएँ सक्षम करें",
"botToken": "Bot Token",
"botTokenHint": "Bot बनाने और Token प्राप्त करने के लिए Telegram में @BotFather खोजें",
"chatId": "Chat ID",
"chatIdHint": "उपयोगकर्ता या समूह ID। बॉट को संदेश भेजें और इसे खोजने के लिए API या @userinfobot का उपयोग करें।",
"webhookBtn": "Webhook सेट करें",
"webhookSetting": "सेट हो रहा है...",
"testBtn": "परीक्षण संदेश भेजें",
"testingBtn": "भेजा जा रहा है...",
"save": "सहेजें",
"saving": "सहेजा जा रहा है..."
},
"display": {
"title": "प्रदर्शन सेटिंग्स",
"layoutTitle": "व्यवस्थापक पैनल शीर्षक",
"layoutTitleHint": "शीर्ष नेविगेशन बार में प्रदर्शित होता है।",
"adminLanguage": "व्यवस्थापक पैनल भाषा",
"adminLanguageHint": "व्यवस्थापक सिस्टम की भाषा सेट करें।",
"save": "सहेजें",
"saving": "सहेजा जा रहा है..."
}
},
"common": {
"saveSuccess": "सफलतापूर्वक सहेजा गया",
"saveFailed": "सहेजने में विफल",
"loading": "लोड हो रहा है..."
}
}

View File

@@ -0,0 +1,304 @@
{
"menu": {
"comments": "Manajemen Komentar",
"stats": "Dasbor",
"analytics": "Analitik Kunjungan",
"settings": "Pengaturan Situs",
"data": "Manajemen Data"
},
"layout": {
"toggleMenu": "Alihkan Menu",
"docs": "Dokumentasi",
"logout": "Keluar",
"moreActions": "Tindakan Lainnya",
"defaultSite": "Situs Default",
"theme": {
"light": "Terang",
"dark": "Gelap",
"system": "Sistem"
},
"version": {
"title": "Informasi Versi",
"apiAddress": "Alamat API",
"apiVersion": "Versi API",
"adminVersion": "Versi Admin",
"match": "Versi admin saat ini cocok dengan versi API, Anda dapat menggunakannya dengan normal.",
"mismatch": "Versi admin saat ini tidak cocok dengan versi API. Disarankan untuk memperbarui layanan API untuk menghindari masalah kompatibilitas.",
"fetchError": "Gagal mengambil versi API:",
"loading": "Memuat...",
"notConfigured": "Belum Dikonfigurasi",
"notFetched": "Tidak Diambil",
"ok": "Mengerti"
}
},
"stats": {
"title": "Dasbor",
"overview": "Ikhtisar",
"total": "Total Komentar",
"approved": "Disetujui",
"pending": "Tertunda",
"rejected": "Ditolak",
"trend": "Tren Komentar",
"bySite": "Statistik per Situs",
"last7Days": "7 Hari Terakhir",
"last30Days": "30 Hari Terakhir",
"noData": "Tidak ada data komentar",
"table": {
"domain": "Domain",
"total": "Total",
"approved": "Disetujui",
"pending": "Tertunda",
"rejected": "Ditolak"
},
"loadError": "Gagal memuat statistik"
},
"analytics": {
"title": "Analitik Kunjungan",
"overview": "Ikhtisar",
"totalPv": "Total Kunjungan",
"todayPv": "Kunjungan Hari Ini",
"weekPv": "Kunjungan Minggu Ini",
"monthPv": "Kunjungan Bulan Ini",
"totalPages": "Halaman Dikunjungi",
"trend": "Tren Kunjungan",
"pageDetail": "Detail Halaman",
"likeRank": "Peringkat Suka",
"last7Days": "7 Hari Terakhir",
"last30Days": "30 Hari Terakhir",
"sort": {
"pv": "Urutkan berdasarkan PV",
"latest": "Kunjungan Terbaru"
},
"table": {
"title": "Judul Halaman",
"pv": "Kunjungan",
"time": "Kunjungan Terakhir",
"url": "URL Halaman",
"rank": "Peringkat",
"like": "Suka"
},
"noData": "Tidak ada data kunjungan",
"noLikeData": "Tidak ada data suka",
"loadError": "Gagal memuat analitik kunjungan"
},
"data": {
"title": "Manajemen Data",
"sections": {
"comments": {
"title": "Data Komentar",
"desc": "Kelola konten komentar, dukung migrasi dari sistem komentar lain.",
"exportLabel": "Ekspor:",
"exporting": "Mengekspor...",
"exportJson": "Ekspor JSON",
"importLabel": "Impor:",
"importButton": "Impor Komentar",
"source": {
"cwd": "CWD (.json)",
"twikoo": "Twikoo (.json)",
"artalk": "Artalk (.json)"
}
},
"config": {
"title": "Konfigurasi Sistem",
"desc": "Kelola pengaturan admin, konfigurasi email, daftar hitam, dll.",
"export": "Ekspor Konfigurasi",
"import": "Impor Konfigurasi"
},
"stats": {
"title": "Statistik Kunjungan",
"desc": "Kelola kunjungan artikel, suka, dan tren kunjungan harian.",
"export": "Ekspor Statistik",
"import": "Impor Statistik"
},
"backup": {
"title": "Cadangan Lengkap",
"desc": "Cadangkan atau pulihkan semua data sistem (Komentar + Konfigurasi + Statistik).",
"export": "Ekspor Lengkap",
"import": "Pulihkan Lengkap"
}
},
"logs": {
"title": "Log Operasi"
},
"prefixModal": {
"title": "Awalan URL Hilang",
"descPart1": "Terdeteksi ",
"descPart2": " komentar tanpa awalan domain (http/https).",
"descPart3": "Apakah Anda ingin menambahkannya saat mengimpor?",
"label": "Awalan Domain (contoh: https://example.me)",
"placeholder": "Masukkan awalan domain",
"skip": "Impor Langsung (Tanpa Menambahkan)",
"confirm": "Tambahkan dan Impor"
},
"messages": {
"exportSuccess": "Ekspor Berhasil",
"exportFailed": "Ekspor Gagal",
"fileReadFailed": "Gagal Membaca File",
"jsonParseFailed": "Gagal Mengurai JSON, silakan periksa format file",
"importConfigSuccess": "Impor Konfigurasi Berhasil",
"importStatsSuccess": "Impor Statistik Berhasil",
"importBackupSuccess": "Pemulihan Lengkap Berhasil",
"importCommentsSuccess": "Impor Komentar Berhasil",
"prefixRequired": "Silakan masukkan awalan domain",
"importStart": "Mulai Impor: {name} (Mode: {mode})",
"fileParseSuccess": "File berhasil diurai, memproses...",
"errorWithMessage": "Kesalahan: {msg}",
"parsedCommentsCount": "Mengurai {count} data komentar",
"detectMissingPrefix": "Terdeteksi {count} URL tanpa awalan, menunggu konfirmasi pengguna...",
"prefixAdded": "Awalan ditambahkan, melanjutkan impor...",
"skipPrefix": "Pengguna melewati penambahan awalan",
"readFileFailedLog": "Gagal membaca file",
"importCommentsDone": "Impor Selesai: {message}"
}
},
"comments": {
"title": "Manajemen Komentar",
"statusFilter": {
"all": "Semua Status",
"approved": "Disetujui",
"pending": "Tertunda",
"rejected": "Ditolak"
},
"refresh": "Segarkan",
"table": {
"author": "Pengguna",
"content": "Konten",
"path": "Jalur",
"status": "Status",
"actions": "Tindakan",
"admin": "Admin",
"time": "Waktu",
"ip": "IP",
"email": "Email"
},
"actions": {
"approve": "Setujui",
"pending": "Tunda",
"reject": "Tolak",
"edit": "Edit",
"delete": "Hapus",
"blockIp": "Blokir IP",
"blockEmail": "Blokir Email",
"pin": "Sematkan"
},
"pagination": {
"prev": "Sebelumnya",
"next": "Berikutnya",
"jumpTo": "Lompat ke",
"page": "Halaman",
"confirm": "Konfirmasi"
},
"confirmDelete": "Apakah Anda yakin ingin menghapus komentar {id}?",
"confirmBlockIp": "Apakah Anda yakin ingin memblokir IP {ip}?",
"confirmBlockEmail": "Apakah Anda yakin ingin memblokir email {email}?",
"successBlockIp": "IP telah ditambahkan ke daftar hitam",
"successBlockEmail": "Email telah ditambahkan ke daftar hitam",
"empty": "Tidak ada data",
"editModal": {
"title": "Edit Komentar",
"name": "Nama Pengunjung",
"email": "Email Pengunjung",
"url": "URL Pengunjung",
"postSlug": "ID Halaman",
"postUrl": "URL Komentar",
"content": "Konten",
"status": "Status",
"priority": "Prioritas (1 default, lebih tinggi lebih dulu)",
"cancel": "Batal",
"save": "Simpan",
"saving": "Menyimpan..."
}
},
"settings": {
"title": "Pengaturan Situs",
"tabs": {
"comment": "Komentar & Keamanan",
"feature": "Fitur",
"emailNotify": "Notifikasi Email",
"telegramNotify": "Notifikasi Telegram",
"display": "Tampilan"
},
"comment": {
"title": "Konfigurasi Tampilan Komentar",
"adminEmail": "Email Admin",
"adminBadge": "Teks Lencana Admin (kosongkan untuk ikon default)",
"adminEnabled": "Tampilkan Lencana Admin",
"avatarPrefix": "Awalan Avatar (Default: https://gravatar.com/avatar)",
"securityTitle": "Pengaturan Keamanan",
"requireReview": "Komentar baru memerlukan persetujuan",
"adminKey": "Kunci Komentar Admin (diperlukan saat menggunakan email admin)",
"adminKeyPlaceholder": "Masukkan kunci untuk mengatur atau mengubah",
"allowedDomains": "Domain yang Diizinkan (kosongkan untuk semua, pisahkan dengan koma atau spasi)",
"blockedIps": "Daftar Hitam IP (pisahkan dengan koma atau baris baru)",
"blockedEmails": "Daftar Hitam Email (pisahkan dengan koma atau baris baru)",
"save": "Simpan",
"saving": "Menyimpan..."
},
"feature": {
"title": "Fitur",
"articleLike": "Aktifkan Suka Artikel",
"articleLikeHint": "Menampilkan tombol suka artikel di atas bagian komentar.",
"commentLike": "Aktifkan Suka Komentar",
"commentLikeHint": "Menampilkan tombol suka pada setiap komentar.",
"imageLightbox": "Lightbox Gambar",
"imageLightboxHint": "Klik gambar dalam komentar untuk melihat layar penuh.",
"placeholder": "Teks Placeholder Komentar",
"placeholderHint": "Teks kustom untuk kotak input. Kosongkan untuk default.",
"widgetLanguage": "Bahasa Widget",
"widgetLanguageHint": "Atur bahasa widget komentar frontend.",
"save": "Simpan",
"saving": "Menyimpan..."
},
"emailNotify": {
"title": "Pengaturan Email",
"enable": "Aktifkan Notifikasi Email",
"smtpTitle": "1. Konfigurasi SMTP",
"provider": "Penyedia",
"host": "Host SMTP",
"port": "Port SMTP",
"secure": "Koneksi SSL",
"user": "Akun Email",
"pass": "Kata Sandi/Kata Sandi Aplikasi",
"qqHint": "Catatan: Untuk QQ Mail, gunakan kode otorisasi, bukan kata sandi.",
"163Hint": "Catatan: Untuk 163 Mail, gunakan kode otorisasi, bukan kata sandi.",
"templateTitle": "2. Template Email",
"adminTemplate": "Notifikasi Admin (HTML)",
"adminTemplateHint": "Variabel: ${commentAuthor}, ${postTitle}, ${postUrl}, ${commentContent}",
"replyTemplate": "Notifikasi Balasan (HTML)",
"replyTemplateHint": "Variabel: ${toName}, ${replyAuthor}, ${postTitle}, ${postUrl}, ${parentComment}, ${replyContent}",
"testBtn": "Kirim Email Uji",
"testingBtn": "Mengirim...",
"resetBtn": "Atur Ulang Template",
"save": "Simpan Konfigurasi",
"saving": "Menyimpan..."
},
"telegramNotify": {
"title": "Pengaturan Telegram",
"enable": "Aktifkan Notifikasi Telegram",
"botToken": "Token Bot",
"botTokenHint": "Cari @BotFather di Telegram untuk membuat bot dan mendapatkan token",
"chatId": "ID Obrolan",
"chatIdHint": "ID Pengguna atau Grup. Kirim pesan ke bot dan gunakan API atau @userinfobot untuk menemukannya.",
"webhookBtn": "Atur Webhook",
"webhookSetting": "Mengatur...",
"testBtn": "Kirim Pesan Uji",
"testingBtn": "Mengirim...",
"save": "Simpan",
"saving": "Menyimpan..."
},
"display": {
"title": "Pengaturan Tampilan",
"layoutTitle": "Judul Panel Admin",
"layoutTitleHint": "Ditampilkan di bilah navigasi atas.",
"adminLanguage": "Bahasa Panel Admin",
"adminLanguageHint": "Atur bahasa sistem admin.",
"save": "Simpan",
"saving": "Menyimpan..."
}
},
"common": {
"saveSuccess": "Berhasil Disimpan",
"saveFailed": "Gagal Disimpan",
"loading": "Memuat..."
}
}

View File

@@ -0,0 +1,304 @@
{
"menu": {
"comments": "Gestione Commenti",
"stats": "Dashboard",
"analytics": "Analitica",
"settings": "Impostazioni",
"data": "Dati"
},
"layout": {
"toggleMenu": "Attiva/Disattiva Menu",
"docs": "Documentazione",
"logout": "Esci",
"moreActions": "Altre Azioni",
"defaultSite": "Sito Predefinito",
"theme": {
"light": "Chiaro",
"dark": "Scuro",
"system": "Sistema"
},
"version": {
"title": "Info Versione",
"apiAddress": "Indirizzo API",
"apiVersion": "Versione API",
"adminVersion": "Versione Admin",
"match": "Le versioni Admin e API corrispondono.",
"mismatch": "Versione non corrispondente. Aggiorna il servizio API.",
"fetchError": "Errore recupero versione:",
"loading": "Caricamento...",
"notConfigured": "Non Configurato",
"notFetched": "Non Recuperato",
"ok": "OK"
}
},
"stats": {
"title": "Dashboard",
"overview": "Panoramica",
"total": "Totale Commenti",
"approved": "Approvati",
"pending": "In Attesa",
"rejected": "Rifiutati",
"trend": "Tendenza",
"bySite": "Per Sito",
"last7Days": "Ultimi 7 Giorni",
"last30Days": "Ultimi 30 Giorni",
"noData": "Nessun Dato",
"table": {
"domain": "Dominio",
"total": "Totale",
"approved": "Approvati",
"pending": "In Attesa",
"rejected": "Rifiutati"
},
"loadError": "Errore caricamento statistiche"
},
"analytics": {
"title": "Analitica",
"overview": "Panoramica",
"totalPv": "Totale PV",
"todayPv": "PV Oggi",
"weekPv": "PV Settimana",
"monthPv": "PV Mese",
"totalPages": "Pagine Visitate",
"trend": "Tendenza Traffico",
"pageDetail": "Dettagli Pagina",
"likeRank": "Top Mi Piace",
"last7Days": "Ultimi 7 Giorni",
"last30Days": "Ultimi 30 Giorni",
"sort": {
"pv": "Ordina per PV",
"latest": "Ultima Visita"
},
"table": {
"title": "Titolo Pagina",
"pv": "PV",
"time": "Ultima Visita",
"url": "URL",
"rank": "Rango",
"like": "Mi Piace"
},
"noData": "Nessun Dato",
"noLikeData": "Nessun Dato Mi Piace",
"loadError": "Errore caricamento analitica"
},
"data": {
"title": "Gestione Dati",
"sections": {
"comments": {
"title": "Dati Commenti",
"desc": "Gestisci e importa commenti.",
"exportLabel": "Esporta:",
"exporting": "Esportazione...",
"exportJson": "Esporta JSON",
"importLabel": "Importa:",
"importButton": "Importa Commenti",
"source": {
"cwd": "CWD (.json)",
"twikoo": "Twikoo (.json)",
"artalk": "Artalk (.json)"
}
},
"config": {
"title": "Config Sistema",
"desc": "Impostazioni, email, blacklist, ecc.",
"export": "Esporta Config",
"import": "Importa Config"
},
"stats": {
"title": "Dati Analitici",
"desc": "Visualizzazioni, mi piace e tendenze.",
"export": "Esporta Stats",
"import": "Importa Stats"
},
"backup": {
"title": "Backup Completo",
"desc": "Backup o ripristino completo (Commenti + Config + Stats).",
"export": "Esportazione Completa",
"import": "Ripristino Completo"
}
},
"logs": {
"title": "Log Operazioni"
},
"prefixModal": {
"title": "Prefisso URL Mancante",
"descPart1": "Rilevati ",
"descPart2": " commenti senza prefisso protocollo (http/https).",
"descPart3": "Aggiungere durante importazione?",
"label": "Prefisso Dominio (es. https://example.me)",
"placeholder": "Inserisci prefisso",
"skip": "Salta",
"confirm": "Aggiungi & Importa"
},
"messages": {
"exportSuccess": "Esportazione Riuscita",
"exportFailed": "Esportazione Fallita",
"fileReadFailed": "Lettura File Fallita",
"jsonParseFailed": "Analisi JSON Fallita",
"importConfigSuccess": "Config Importata",
"importStatsSuccess": "Stats Importate",
"importBackupSuccess": "Ripristino Completo Riuscito",
"importCommentsSuccess": "Commenti Importati",
"prefixRequired": "Inserisci prefisso",
"importStart": "Importazione: {name} (Modo: {mode})",
"fileParseSuccess": "File analizzato, elaborazione...",
"errorWithMessage": "Errore: {msg}",
"parsedCommentsCount": "{count} commenti analizzati",
"detectMissingPrefix": "{count} URL senza prefisso, in attesa...",
"prefixAdded": "Prefisso aggiunto, continuo...",
"skipPrefix": "Aggiunta prefisso saltata",
"readFileFailedLog": "Lettura file fallita",
"importCommentsDone": "Importazione Completata: {message}"
}
},
"comments": {
"title": "Gestione Commenti",
"statusFilter": {
"all": "Tutti",
"approved": "Approvati",
"pending": "In Attesa",
"rejected": "Rifiutati"
},
"refresh": "Aggiorna",
"table": {
"author": "Utente",
"content": "Commento",
"path": "Percorso",
"status": "Stato",
"actions": "Azioni",
"admin": "Admin",
"time": "Ora",
"ip": "IP",
"email": "Email"
},
"actions": {
"approve": "Approva",
"pending": "Sospendi",
"reject": "Rifiuta",
"edit": "Modifica",
"delete": "Elimina",
"blockIp": "Blocca IP",
"blockEmail": "Blocca Email",
"pin": "Fissa"
},
"pagination": {
"prev": "Prec",
"next": "Succ",
"jumpTo": "Vai a",
"page": "Pagina",
"confirm": "OK"
},
"confirmDelete": "Eliminare commento {id}?",
"confirmBlockIp": "Bloccare IP {ip}?",
"confirmBlockEmail": "Bloccare email {email}?",
"successBlockIp": "IP Bloccato",
"successBlockEmail": "Email Bloccata",
"empty": "Nessun Dato",
"editModal": {
"title": "Modifica Commento",
"name": "Nome",
"email": "Email",
"url": "URL",
"postSlug": "Slug",
"postUrl": "URL Post",
"content": "Contenuto",
"status": "Stato",
"priority": "Priorità (1=Normale, maggiore=Alto)",
"cancel": "Annulla",
"save": "Salva",
"saving": "Salvataggio..."
}
},
"settings": {
"title": "Impostazioni",
"tabs": {
"comment": "Commenti & Sicurezza",
"feature": "Funzionalità",
"emailNotify": "Notifica Email",
"telegramNotify": "Notifica Telegram",
"display": "Visualizzazione"
},
"comment": {
"title": "Visualizzazione Commenti",
"adminEmail": "Email Admin",
"adminBadge": "Testo Badge Admin (Vuoto per icona default)",
"adminEnabled": "Mostra Badge Admin",
"avatarPrefix": "Prefisso Avatar (Default: https://gravatar.com/avatar)",
"securityTitle": "Sicurezza",
"requireReview": "Richiedi Revisione per Nuovi Commenti",
"adminKey": "Chiave Admin (Richiesta per commenti con email admin)",
"adminKeyPlaceholder": "Inserisci chiave",
"allowedDomains": "Domini Consentiti (Vuoto per tutti. Separare con spazio/virgola)",
"blockedIps": "IP Bloccati (Separare con virgola/nuova riga)",
"blockedEmails": "Email Bloccate (Separare con virgola/nuova riga)",
"save": "Salva",
"saving": "Salvataggio..."
},
"feature": {
"title": "Funzionalità",
"articleLike": "Abilita Mi Piace Articolo",
"articleLikeHint": "Mostra pulsante Mi Piace sopra i commenti.",
"commentLike": "Abilita Mi Piace Commento",
"commentLikeHint": "Mostra pulsante Mi Piace su ogni commento.",
"imageLightbox": "Lightbox Immagine",
"imageLightboxHint": "Abilita anteprima a schermo intero per le immagini.",
"placeholder": "Testo Placeholder",
"placeholderHint": "Placeholder personalizzato per input commenti.",
"widgetLanguage": "Lingua Widget",
"widgetLanguageHint": "Lingua per il widget frontend.",
"save": "Salva",
"saving": "Salvataggio..."
},
"emailNotify": {
"title": "Notifica Email",
"enable": "Abilita Notifica Email",
"smtpTitle": "1. Config SMTP",
"provider": "Provider",
"host": "Host SMTP",
"port": "Porta SMTP",
"secure": "Sicurezza SSL",
"user": "Utente Email",
"pass": "Password/Token App",
"qqHint": "Nota: Per QQ Mail, usa Token App.",
"163Hint": "Nota: Per 163 Mail, usa Token App.",
"templateTitle": "2. Config Template",
"adminTemplate": "Template Admin (HTML)",
"adminTemplateHint": "Vars: ${commentAuthor}, ${postTitle}, ${postUrl}, ${commentContent}",
"replyTemplate": "Template Risposta (HTML)",
"replyTemplateHint": "Vars: ${toName}, ${replyAuthor}, ${postTitle}, ${postUrl}, ${parentComment}, ${replyContent}",
"testBtn": "Invia Email Test",
"testingBtn": "Invio...",
"resetBtn": "Reimposta Template",
"save": "Salva Config",
"saving": "Salvataggio..."
},
"telegramNotify": {
"title": "Notifica Telegram",
"enable": "Abilita Notifica Telegram",
"botToken": "Token Bot",
"botTokenHint": "Ottieni da @BotFather",
"chatId": "ID Chat",
"chatIdHint": "ID Utente o Gruppo. Usa @userinfobot.",
"webhookBtn": "Imposta Webhook",
"webhookSetting": "Configurazione...",
"testBtn": "Invia Messaggio Test",
"testingBtn": "Invio...",
"save": "Salva",
"saving": "Salvataggio..."
},
"display": {
"title": "Impostazioni Visualizzazione",
"layoutTitle": "Titolo Admin",
"layoutTitleHint": "Visualizzato nella barra di navigazione superiore.",
"adminLanguage": "Lingua Admin",
"adminLanguageHint": "Lingua per il pannello di amministrazione.",
"save": "Salva",
"saving": "Salvataggio..."
},
"common": {
"saveSuccess": "Salvato con Successo",
"saveFailed": "Salvataggio Fallito",
"loading": "Caricamento..."
}
}
}

View File

@@ -0,0 +1,304 @@
{
"menu": {
"comments": "コメント管理",
"stats": "ダッシュボード",
"analytics": "アクセス統計",
"settings": "サイト設定",
"data": "データ管理"
},
"layout": {
"toggleMenu": "メニュー切り替え",
"docs": "ドキュメント",
"logout": "ログアウト",
"moreActions": "その他の操作",
"defaultSite": "デフォルトサイト",
"theme": {
"light": "ライト",
"dark": "ダーク",
"system": "システム"
},
"version": {
"title": "バージョン情報",
"apiAddress": "API アドレス",
"apiVersion": "API バージョン",
"adminVersion": "管理画面バージョン",
"match": "管理画面とAPIのバージョンが一致しています。",
"mismatch": "バージョンが一致しません。互換性の問題を避けるため、APIサービスを更新してください。",
"fetchError": "バージョン情報の取得に失敗しました:",
"loading": "読み込み中...",
"notConfigured": "未設定",
"notFetched": "未取得",
"ok": "OK"
}
},
"stats": {
"title": "ダッシュボード",
"overview": "概要",
"total": "総コメント数",
"approved": "承認済み",
"pending": "承認待ち",
"rejected": "却下済み",
"trend": "コメント傾向",
"bySite": "サイト別統計",
"last7Days": "過去7日間",
"last30Days": "過去30日間",
"noData": "データなし",
"table": {
"domain": "ドメイン",
"total": "合計",
"approved": "承認済み",
"pending": "承認待ち",
"rejected": "却下済み"
},
"loadError": "統計データの読み込みに失敗しました"
},
"analytics": {
"title": "アクセス統計",
"overview": "概要",
"totalPv": "総PV",
"todayPv": "今日のPV",
"weekPv": "今週のPV",
"monthPv": "今月のPV",
"totalPages": "訪問ページ数",
"trend": "アクセス傾向",
"pageDetail": "ページ詳細",
"likeRank": "いいねランキング",
"last7Days": "過去7日間",
"last30Days": "過去30日間",
"sort": {
"pv": "PV順",
"latest": "最新の訪問"
},
"table": {
"title": "ページタイトル",
"pv": "PV",
"time": "最終訪問",
"url": "URL",
"rank": "順位",
"like": "いいね"
},
"noData": "データなし",
"noLikeData": "いいねデータなし",
"loadError": "統計データの読み込みに失敗しました"
},
"data": {
"title": "データ管理",
"sections": {
"comments": {
"title": "コメントデータ",
"desc": "コメントの管理と他のフレームワークからのインポート。",
"exportLabel": "エクスポート:",
"exporting": "エクスポート中...",
"exportJson": "JSONエクスポート",
"importLabel": "インポート:",
"importButton": "コメントをインポート",
"source": {
"cwd": "CWD (.json)",
"twikoo": "Twikoo (.json)",
"artalk": "Artalk (.json)"
}
},
"config": {
"title": "システム設定",
"desc": "管理画面設定、メール設定、ブラックリストなど。",
"export": "設定エクスポート",
"import": "設定インポート"
},
"stats": {
"title": "アクセス統計",
"desc": "PV、いいね、アクセス傾向の管理。",
"export": "統計エクスポート",
"import": "統計インポート"
},
"backup": {
"title": "完全バックアップ",
"desc": "全システムデータのバックアップと復元(コメント+設定+統計)。",
"export": "完全エクスポート",
"import": "完全復元"
}
},
"logs": {
"title": "操作ログ"
},
"prefixModal": {
"title": "URLプレフィックス欠落を検出",
"descPart1": "検出された ",
"descPart2": " 件のコメントURLにプロトコルプレフィックス(http/https)がありません。",
"descPart3": "インポート時に追加しますか?",
"label": "ドメインプレフィックス (例: https://example.me)",
"placeholder": "プレフィックスを入力",
"skip": "スキップ (追加しない)",
"confirm": "追加してインポート"
},
"messages": {
"exportSuccess": "エクスポート成功",
"exportFailed": "エクスポート失敗",
"fileReadFailed": "ファイル読み込み失敗",
"jsonParseFailed": "JSON解析失敗",
"importConfigSuccess": "設定インポート成功",
"importStatsSuccess": "統計インポート成功",
"importBackupSuccess": "完全復元成功",
"importCommentsSuccess": "コメントインポート成功",
"prefixRequired": "ドメインプレフィックスを入力してください",
"importStart": "インポート開始: {name} (モード: {mode})",
"fileParseSuccess": "ファイル解析成功、処理中...",
"errorWithMessage": "エラー: {msg}",
"parsedCommentsCount": "{count} 件のコメントを解析しました",
"detectMissingPrefix": "{count} 件のURL欠落を検出、確認待ち...",
"prefixAdded": "プレフィックス追加済み、続行中...",
"skipPrefix": "プレフィックス追加をスキップ",
"readFileFailedLog": "ファイル読み込み失敗",
"importCommentsDone": "インポート完了: {message}"
}
},
"comments": {
"title": "コメント管理",
"statusFilter": {
"all": "すべて",
"approved": "承認済み",
"pending": "承認待ち",
"rejected": "却下済み"
},
"refresh": "更新",
"table": {
"author": "ユーザー",
"content": "コメント",
"path": "パス",
"status": "ステータス",
"actions": "操作",
"admin": "管理者",
"time": "時間",
"ip": "IP",
"email": "メール"
},
"actions": {
"approve": "承認",
"pending": "保留",
"reject": "却下",
"edit": "編集",
"delete": "削除",
"blockIp": "IPブロック",
"blockEmail": "メールブロック",
"pin": "固定"
},
"pagination": {
"prev": "前へ",
"next": "次へ",
"jumpTo": "移動",
"page": "ページ",
"confirm": "OK"
},
"confirmDelete": "コメント {id} を削除しますか?",
"confirmBlockIp": "IP {ip} をブラックリストに追加しますか?",
"confirmBlockEmail": "メール {email} をブラックリストに追加しますか?",
"successBlockIp": "IPをブロックしました",
"successBlockEmail": "メールをブロックしました",
"empty": "データなし",
"editModal": {
"title": "コメント編集",
"name": "名前",
"email": "メール",
"url": "URL",
"postSlug": "スラッグ",
"postUrl": "記事URL",
"content": "内容",
"status": "ステータス",
"priority": "優先度 (1=通常, 大きいほど上)",
"cancel": "キャンセル",
"save": "保存",
"saving": "保存中..."
}
},
"settings": {
"title": "設定",
"tabs": {
"comment": "コメントとセキュリティ",
"feature": "機能設定",
"emailNotify": "メール通知",
"telegramNotify": "Telegram通知",
"display": "表示設定"
},
"comment": {
"title": "コメント表示設定",
"adminEmail": "管理者メール",
"adminBadge": "管理者バッジ文字 (空の場合はデフォルトアイコン)",
"adminEnabled": "管理者バッジを表示",
"avatarPrefix": "アバタープレフィックス (デフォルト: https://gravatar.com/avatar)",
"securityTitle": "セキュリティ設定",
"requireReview": "新規コメントを承認制にする",
"adminKey": "管理者キー (管理者メールでのコメントに必要)",
"adminKeyPlaceholder": "キーを入力",
"allowedDomains": "許可ドメイン (空の場合は制限なし。スペースまたはカンマ区切り)",
"blockedIps": "ブロックIP (カンマまたは改行区切り)",
"blockedEmails": "ブロックメール (カンマまたは改行区切り)",
"save": "保存",
"saving": "保存中..."
},
"feature": {
"title": "機能設定",
"articleLike": "記事へのいいねを有効化",
"articleLikeHint": "コメント欄上部に記事へのいいねボタンを表示します。",
"commentLike": "コメントへのいいねを有効化",
"commentLikeHint": "各コメントにいいねボタンを表示します。",
"imageLightbox": "画像ライトボックス",
"imageLightboxHint": "コメント内の画像を全画面プレビューします。",
"placeholder": "プレースホルダー",
"placeholderHint": "コメント入力欄のヒントテキスト。",
"widgetLanguage": "ウィジェット言語",
"widgetLanguageHint": "フロントエンドウィジェットの言語。",
"save": "保存",
"saving": "保存中..."
},
"emailNotify": {
"title": "メール通知設定",
"enable": "メール通知を有効化",
"smtpTitle": "1. SMTP設定",
"provider": "プロバイダー",
"host": "SMTPホスト",
"port": "SMTPポート",
"secure": "SSL接続",
"user": "メールユーザー",
"pass": "パスワード/アプリパスワード",
"qqHint": "注意: QQメールはパスワードではなく認証コードを使用してください。",
"163Hint": "注意: 163メールはパスワードではなく認証コードを使用してください。",
"templateTitle": "2. テンプレート設定",
"adminTemplate": "管理者通知テンプレート (HTML)",
"adminTemplateHint": "変数: ${commentAuthor}, ${postTitle}, ${postUrl}, ${commentContent}",
"replyTemplate": "返信通知テンプレート (HTML)",
"replyTemplateHint": "変数: ${toName}, ${replyAuthor}, ${postTitle}, ${postUrl}, ${parentComment}, ${replyContent}",
"testBtn": "テストメール送信",
"testingBtn": "送信中...",
"resetBtn": "テンプレートをリセット",
"save": "設定を保存",
"saving": "保存中..."
},
"telegramNotify": {
"title": "Telegram通知設定",
"enable": "Telegram通知を有効化",
"botToken": "Bot Token",
"botTokenHint": "@BotFather から取得",
"chatId": "Chat ID",
"chatIdHint": "ユーザーIDまたはグループID。@userinfobot で確認できます。",
"webhookBtn": "Webhook設定",
"webhookSetting": "設定中...",
"testBtn": "テストメッセージ送信",
"testingBtn": "送信中...",
"save": "保存",
"saving": "保存中..."
},
"display": {
"title": "表示設定",
"layoutTitle": "管理画面タイトル",
"layoutTitleHint": "トップナビゲーションバーに表示されます。",
"adminLanguage": "管理画面言語",
"adminLanguageHint": "管理パネルの言語設定。",
"save": "保存",
"saving": "保存中..."
},
"common": {
"saveSuccess": "保存成功",
"saveFailed": "保存失敗",
"loading": "読み込み中..."
}
}
}

View File

@@ -0,0 +1,304 @@
{
"menu": {
"comments": "댓글 관리",
"stats": "대시보드",
"analytics": "방문 통계",
"settings": "사이트 설정",
"data": "데이터 관리"
},
"layout": {
"toggleMenu": "메뉴 전환",
"docs": "문서",
"logout": "로그아웃",
"moreActions": "더보기",
"defaultSite": "기본 사이트",
"theme": {
"light": "라이트 모드",
"dark": "다크 모드",
"system": "시스템 설정"
},
"version": {
"title": "버전 정보",
"apiAddress": "API 주소",
"apiVersion": "API 버전",
"adminVersion": "관리자 버전",
"match": "관리자와 API 버전이 일치합니다.",
"mismatch": "버전 불일치. 호환성 문제를 방지하기 위해 API 서비스를 업데이트하세요.",
"fetchError": "버전 정보를 가져올 수 없습니다:",
"loading": "로딩 중...",
"notConfigured": "설정되지 않음",
"notFetched": "가져오지 못함",
"ok": "확인"
}
},
"stats": {
"title": "대시보드",
"overview": "개요",
"total": "총 댓글 수",
"approved": "승인됨",
"pending": "대기 중",
"rejected": "거절됨",
"trend": "댓글 추세",
"bySite": "사이트별 통계",
"last7Days": "최근 7일",
"last30Days": "최근 30일",
"noData": "데이터 없음",
"table": {
"domain": "도메인",
"total": "합계",
"approved": "승인됨",
"pending": "대기 중",
"rejected": "거절됨"
},
"loadError": "통계 로드 실패"
},
"analytics": {
"title": "방문 통계",
"overview": "개요",
"totalPv": "총 PV",
"todayPv": "오늘 PV",
"weekPv": "이번 주 PV",
"monthPv": "이번 달 PV",
"totalPages": "방문한 페이지 수",
"trend": "트래픽 추세",
"pageDetail": "페이지 상세",
"likeRank": "좋아요 순위",
"last7Days": "최근 7일",
"last30Days": "최근 30일",
"sort": {
"pv": "PV순 정렬",
"latest": "최신 방문"
},
"table": {
"title": "페이지 제목",
"pv": "PV",
"time": "마지막 방문",
"url": "URL",
"rank": "순위",
"like": "좋아요"
},
"noData": "데이터 없음",
"noLikeData": "좋아요 데이터 없음",
"loadError": "통계 로드 실패"
},
"data": {
"title": "데이터 관리",
"sections": {
"comments": {
"title": "댓글 데이터",
"desc": "댓글 관리 및 타 프레임워크에서 가져오기.",
"exportLabel": "내보내기:",
"exporting": "내보내는 중...",
"exportJson": "JSON 내보내기",
"importLabel": "가져오기:",
"importButton": "댓글 가져오기",
"source": {
"cwd": "CWD (.json)",
"twikoo": "Twikoo (.json)",
"artalk": "Artalk (.json)"
}
},
"config": {
"title": "시스템 설정",
"desc": "관리자 설정, 이메일 설정, 차단 목록 등.",
"export": "설정 내보내기",
"import": "설정 가져오기"
},
"stats": {
"title": "통계 데이터",
"desc": "페이지 조회수, 좋아요, 추세 관리.",
"export": "통계 내보내기",
"import": "통계 가져오기"
},
"backup": {
"title": "전체 백업",
"desc": "시스템 전체 데이터 백업 및 복원 (댓글 + 설정 + 통계).",
"export": "전체 내보내기",
"import": "전체 복원"
}
},
"logs": {
"title": "작업 로그"
},
"prefixModal": {
"title": "URL 접두사 누락 감지됨",
"descPart1": "감지됨: ",
"descPart2": "개의 댓글 URL에 프로토콜 접두사(http/https)가 없습니다.",
"descPart3": "가져올 때 추가하시겠습니까?",
"label": "도메인 접두사 (예: https://example.me)",
"placeholder": "접두사 입력",
"skip": "건너뛰기 (추가 안 함)",
"confirm": "추가 및 가져오기"
},
"messages": {
"exportSuccess": "내보내기 성공",
"exportFailed": "내보내기 실패",
"fileReadFailed": "파일 읽기 실패",
"jsonParseFailed": "JSON 파싱 실패",
"importConfigSuccess": "설정 가져오기 성공",
"importStatsSuccess": "통계 가져오기 성공",
"importBackupSuccess": "전체 복원 성공",
"importCommentsSuccess": "댓글 가져오기 성공",
"prefixRequired": "도메인 접두사를 입력하세요",
"importStart": "가져오기 시작: {name} (모드: {mode})",
"fileParseSuccess": "파일 파싱 성공, 처리 중...",
"errorWithMessage": "오류: {msg}",
"parsedCommentsCount": "{count}개 댓글 파싱됨",
"detectMissingPrefix": "{count}개 URL 누락 감지, 확인 대기 중...",
"prefixAdded": "접두사 추가됨, 계속 진행 중...",
"skipPrefix": "접두사 추가 건너뜀",
"readFileFailedLog": "파일 읽기 실패",
"importCommentsDone": "가져오기 완료: {message}"
}
},
"comments": {
"title": "댓글 관리",
"statusFilter": {
"all": "전체",
"approved": "승인됨",
"pending": "대기 중",
"rejected": "거절됨"
},
"refresh": "새로고침",
"table": {
"author": "사용자",
"content": "내용",
"path": "경로",
"status": "상태",
"actions": "작업",
"admin": "관리자",
"time": "시간",
"ip": "IP",
"email": "이메일"
},
"actions": {
"approve": "승인",
"pending": "보류",
"reject": "거절",
"edit": "편집",
"delete": "삭제",
"blockIp": "IP 차단",
"blockEmail": "이메일 차단",
"pin": "고정"
},
"pagination": {
"prev": "이전",
"next": "다음",
"jumpTo": "이동",
"page": "페이지",
"confirm": "확인"
},
"confirmDelete": "댓글 {id}를 삭제하시겠습니까?",
"confirmBlockIp": "IP {ip}를 차단하시겠습니까?",
"confirmBlockEmail": "이메일 {email}을 차단하시겠습니까?",
"successBlockIp": "IP 차단됨",
"successBlockEmail": "이메일 차단됨",
"empty": "데이터 없음",
"editModal": {
"title": "댓글 편집",
"name": "이름",
"email": "이메일",
"url": "URL",
"postSlug": "슬러그",
"postUrl": "게시물 URL",
"content": "내용",
"status": "상태",
"priority": "우선순위 (1=보통, 높을수록 상단)",
"cancel": "취소",
"save": "저장",
"saving": "저장 중..."
}
},
"settings": {
"title": "설정",
"tabs": {
"comment": "댓글 및 보안",
"feature": "기능",
"emailNotify": "이메일 알림",
"telegramNotify": "Telegram 알림",
"display": "표시 설정"
},
"comment": {
"title": "댓글 표시 설정",
"adminEmail": "관리자 이메일",
"adminBadge": "관리자 배지 텍스트 (기본 아이콘 사용 시 비워둠)",
"adminEnabled": "관리자 배지 표시",
"avatarPrefix": "아바타 접두사 (기본값: https://gravatar.com/avatar)",
"securityTitle": "보안 설정",
"requireReview": "새 댓글 검토 후 표시",
"adminKey": "관리자 키 (관리자 이메일 댓글 작성 시 필요)",
"adminKeyPlaceholder": "키 입력",
"allowedDomains": "허용 도메인 (비워두면 모두 허용. 공백/콤마로 구분)",
"blockedIps": "차단된 IP (콤마/줄바꿈으로 구분)",
"blockedEmails": "차단된 이메일 (콤마/줄바꿈으로 구분)",
"save": "저장",
"saving": "저장 중..."
},
"feature": {
"title": "기능 설정",
"articleLike": "게시물 좋아요 활성화",
"articleLikeHint": "댓글 상단에 좋아요 버튼 표시.",
"commentLike": "댓글 좋아요 활성화",
"commentLikeHint": "각 댓글에 좋아요 버튼 표시.",
"imageLightbox": "이미지 라이트박스",
"imageLightboxHint": "댓글 이미지 클릭 시 전체 화면 미리보기.",
"placeholder": "플레이스홀더",
"placeholderHint": "댓글 입력창 안내 문구.",
"widgetLanguage": "위젯 언어",
"widgetLanguageHint": "프론트엔드 위젯 언어.",
"save": "저장",
"saving": "저장 중..."
},
"emailNotify": {
"title": "이메일 알림",
"enable": "이메일 알림 활성화",
"smtpTitle": "1. SMTP 설정",
"provider": "제공자",
"host": "SMTP 호스트",
"port": "SMTP 포트",
"secure": "SSL 보안",
"user": "이메일 계정",
"pass": "비밀번호/앱 토큰",
"qqHint": "참고: QQ 메일은 비밀번호 대신 앱 토큰을 사용하세요.",
"163Hint": "참고: 163 메일은 비밀번호 대신 앱 토큰을 사용하세요.",
"templateTitle": "2. 템플릿 설정",
"adminTemplate": "관리자 알림 템플릿 (HTML)",
"adminTemplateHint": "변수: ${commentAuthor}, ${postTitle}, ${postUrl}, ${commentContent}",
"replyTemplate": "답글 알림 템플릿 (HTML)",
"replyTemplateHint": "변수: ${toName}, ${replyAuthor}, ${postTitle}, ${postUrl}, ${parentComment}, ${replyContent}",
"testBtn": "테스트 이메일 전송",
"testingBtn": "전송 중...",
"resetBtn": "템플릿 초기화",
"save": "설정 저장",
"saving": "저장 중..."
},
"telegramNotify": {
"title": "Telegram 알림",
"enable": "Telegram 알림 활성화",
"botToken": "Bot Token",
"botTokenHint": "@BotFather에서 획득",
"chatId": "Chat ID",
"chatIdHint": "사용자 ID 또는 그룹 ID. @userinfobot으로 확인.",
"webhookBtn": "웹훅 설정",
"webhookSetting": "설정 중...",
"testBtn": "테스트 메시지 전송",
"testingBtn": "전송 중...",
"save": "저장",
"saving": "저장 중..."
},
"display": {
"title": "표시 설정",
"layoutTitle": "관리자 제목",
"layoutTitleHint": "상단 내비게이션 바에 표시됨.",
"adminLanguage": "관리자 언어",
"adminLanguageHint": "관리자 패널 언어.",
"save": "저장",
"saving": "저장 중..."
},
"common": {
"saveSuccess": "저장 성공",
"saveFailed": "저장 실패",
"loading": "로딩 중..."
}
}
}

View File

@@ -0,0 +1,304 @@
{
"menu": {
"comments": "Reactiebeheer",
"stats": "Dashboard",
"analytics": "Bezoekstatistieken",
"settings": "Instellingen",
"data": "Gegevensbeheer"
},
"layout": {
"toggleMenu": "Menu wisselen",
"docs": "Documentatie",
"logout": "Uitloggen",
"moreActions": "Meer acties",
"defaultSite": "Standaard site",
"theme": {
"light": "Licht",
"dark": "Donker",
"system": "Systeem"
},
"version": {
"title": "Versie-informatie",
"apiAddress": "API-adres",
"apiVersion": "API-versie",
"adminVersion": "Beheerversie",
"match": "De huidige beheerversie komt overeen met de API-versie, u kunt deze normaal gebruiken.",
"mismatch": "De huidige beheerversie komt niet overeen met de API-versie. Het wordt aanbevolen om de API-service bij te werken om compatibiliteitsproblemen te voorkomen.",
"fetchError": "Kan API-versie niet ophalen:",
"loading": "Laden...",
"notConfigured": "Niet geconfigureerd",
"notFetched": "Niet opgehaald",
"ok": "Begrepen"
}
},
"stats": {
"title": "Dashboard",
"overview": "Overzicht",
"total": "Totaal reacties",
"approved": "Goedgekeurd",
"pending": "In afwachting",
"rejected": "Geweigerd",
"trend": "Reactietrend",
"bySite": "Statistieken per site",
"last7Days": "Laatste 7 dagen",
"last30Days": "Laatste 30 dagen",
"noData": "Geen reactiegegevens",
"table": {
"domain": "Domein",
"total": "Totaal",
"approved": "Goedgekeurd",
"pending": "In afwachting",
"rejected": "Geweigerd"
},
"loadError": "Kan statistieken niet laden"
},
"analytics": {
"title": "Bezoekstatistieken",
"overview": "Overzicht",
"totalPv": "Totaal aantal bezoeken",
"todayPv": "Bezoeken vandaag",
"weekPv": "Bezoeken deze week",
"monthPv": "Bezoeken deze maand",
"totalPages": "Pagina's met bezoeken",
"trend": "Bezoektrend",
"pageDetail": "Paginadetails",
"likeRank": "Pagina's met meeste likes",
"last7Days": "Laatste 7 dagen",
"last30Days": "Laatste 30 dagen",
"sort": {
"pv": "Sorteer op PV",
"latest": "Nieuwste bezoek"
},
"table": {
"title": "Paginatitel",
"pv": "Bezoeken",
"time": "Laatste bezoek",
"url": "Pagina-URL",
"rank": "Rang",
"like": "Likes"
},
"noData": "Geen bezoekgegevens",
"noLikeData": "Geen like-gegevens",
"loadError": "Kan bezoekstatistieken niet laden"
},
"data": {
"title": "Gegevensbeheer",
"sections": {
"comments": {
"title": "Reactiegegevens",
"desc": "Beheer reacties, ondersteunt migratie van andere reactiesystemen.",
"exportLabel": "Exporteren:",
"exporting": "Exporteren...",
"exportJson": "JSON exporteren",
"importLabel": "Importeren:",
"importButton": "Reacties importeren",
"source": {
"cwd": "CWD (.json)",
"twikoo": "Twikoo (.json)",
"artalk": "Artalk (.json)"
}
},
"config": {
"title": "Systeemconfiguratie",
"desc": "Beheer systeeminstellingen, e-mailconfiguratie, zwarte lijsten, enz.",
"export": "Configuratie exporteren",
"import": "Configuratie importeren"
},
"stats": {
"title": "Bezoekstatistieken",
"desc": "Beheer paginabezoeken, likes en dagelijkse trends.",
"export": "Statistieken exporteren",
"import": "Statistieken importeren"
},
"backup": {
"title": "Volledige back-up",
"desc": "Back-up of herstel van alle systeemgegevens (reacties + configuratie + statistieken).",
"export": "Volledig exporteren",
"import": "Volledig herstellen"
}
},
"logs": {
"title": "Actielogs"
},
"prefixModal": {
"title": "URL-voorvoegsel ontbreekt",
"descPart1": "Gedetecteerd ",
"descPart2": " reacties zonder domeinvoorvoegsel (http/https).",
"descPart3": "Wilt u dit toevoegen tijdens het importeren?",
"label": "Domeinvoorvoegsel (bijv. https://example.me)",
"placeholder": "Voer domeinvoorvoegsel in",
"skip": "Direct importeren (niet toevoegen)",
"confirm": "Toevoegen en importeren"
},
"messages": {
"exportSuccess": "Exporteren geslaagd",
"exportFailed": "Exporteren mislukt",
"fileReadFailed": "Bestand lezen mislukt",
"jsonParseFailed": "JSON parsen mislukt, controleer het bestandsformaat",
"importConfigSuccess": "Configuratie importeren geslaagd",
"importStatsSuccess": "Statistieken importeren geslaagd",
"importBackupSuccess": "Volledig herstel geslaagd",
"importCommentsSuccess": "Reacties importeren geslaagd",
"prefixRequired": "Voer een domeinvoorvoegsel in",
"importStart": "Start import: {name} (Modus: {mode})",
"fileParseSuccess": "Bestand geparsed, verwerken...",
"errorWithMessage": "Fout: {msg}",
"parsedCommentsCount": "{count} reacties geparsed",
"detectMissingPrefix": "{count} URL's zonder voorvoegsel gedetecteerd, wachten op bevestiging...",
"prefixAdded": "Voorvoegsel toegevoegd, doorgaan met importeren...",
"skipPrefix": "Gebruiker heeft voorvoegsel toevoegen overgeslagen",
"readFileFailedLog": "Bestand lezen mislukt",
"importCommentsDone": "Import voltooid: {message}"
}
},
"comments": {
"title": "Reactiebeheer",
"statusFilter": {
"all": "Alle statussen",
"approved": "Goedgekeurd",
"pending": "In afwachting",
"rejected": "Geweigerd"
},
"refresh": "Vernieuwen",
"table": {
"author": "Gebruiker",
"content": "Inhoud",
"path": "Pad",
"status": "Status",
"actions": "Acties",
"admin": "Beheerder",
"time": "Tijd",
"ip": "IP",
"email": "E-mail"
},
"actions": {
"approve": "Goedkeuren",
"pending": "Wachten",
"reject": "Weigeren",
"edit": "Bewerken",
"delete": "Verwijderen",
"blockIp": "IP blokkeren",
"blockEmail": "E-mail blokkeren",
"pin": "Vastzetten"
},
"pagination": {
"prev": "Vorige",
"next": "Volgende",
"jumpTo": "Ga naar",
"page": "Pagina",
"confirm": "Bevestigen"
},
"confirmDelete": "Weet u zeker dat u reactie {id} wilt verwijderen?",
"confirmBlockIp": "Weet u zeker dat u IP {ip} op de zwarte lijst wilt zetten?",
"confirmBlockEmail": "Weet u zeker dat u e-mail {email} op de zwarte lijst wilt zetten?",
"successBlockIp": "IP toegevoegd aan zwarte lijst",
"successBlockEmail": "E-mail toegevoegd aan zwarte lijst",
"empty": "Geen gegevens",
"editModal": {
"title": "Reactie bewerken",
"name": "Naam bezoeker",
"email": "E-mail bezoeker",
"url": "URL bezoeker",
"postSlug": "Pagina-ID",
"postUrl": "Reactie-URL",
"content": "Inhoud",
"status": "Status",
"priority": "Prioriteit (1 is standaard, hoger is belangrijker)",
"cancel": "Annuleren",
"save": "Opslaan",
"saving": "Opslaan..."
}
},
"settings": {
"title": "Instellingen",
"tabs": {
"comment": "Reacties & Beveiliging",
"feature": "Functies",
"emailNotify": "E-mailmeldingen",
"telegramNotify": "Telegram-meldingen",
"display": "Weergave"
},
"comment": {
"title": "Reactieweergave",
"adminEmail": "E-mailadres beheerder",
"adminBadge": "Beheerderlabel (leeg laten voor standaardpictogram)",
"adminEnabled": "Beheerderlabel weergeven",
"avatarPrefix": "Avatar-voorvoegsel (standaard: https://gravatar.com/avatar)",
"securityTitle": "Beveiligingsinstellingen",
"requireReview": "Nieuwe reacties moeten worden goedgekeurd",
"adminKey": "Beheerderssleutel (vereist voor reacties met beheerders-e-mail)",
"adminKeyPlaceholder": "Voer sleutel in om in te stellen of te wijzigen",
"allowedDomains": "Toegestane domeinen (leeg laten voor alle domeinen, scheiden met komma of spatie)",
"blockedIps": "IP zwarte lijst (scheiden met komma of nieuwe regel)",
"blockedEmails": "E-mail zwarte lijst (scheiden met komma of nieuwe regel)",
"save": "Opslaan",
"saving": "Opslaan..."
},
"feature": {
"title": "Functies",
"articleLike": "Artikel likes inschakelen",
"articleLikeHint": "Toont een like-knop boven de reactiesectie.",
"commentLike": "Reactie likes inschakelen",
"commentLikeHint": "Toont een like-knop bij elke reactie.",
"imageLightbox": "Lightbox voor afbeeldingen",
"imageLightboxHint": "Klik op afbeeldingen in reacties om ze op volledig scherm te bekijken.",
"placeholder": "Plaatsaanduiding invoerveld",
"placeholderHint": "Aangepaste tekst voor het invoerveld. Leeg laten voor standaard.",
"widgetLanguage": "Widgettaal",
"widgetLanguageHint": "Taal van de frontend-widget.",
"save": "Opslaan",
"saving": "Opslaan..."
},
"emailNotify": {
"title": "E-mailinstellingen",
"enable": "E-mailmeldingen inschakelen",
"smtpTitle": "1. SMTP-configuratie",
"provider": "Provider",
"host": "SMTP-host",
"port": "SMTP-poort",
"secure": "SSL-verbinding",
"user": "E-mailadres",
"pass": "Wachtwoord/App-wachtwoord",
"qqHint": "Opmerking: Gebruik voor QQ Mail een autorisatiecode, geen wachtwoord.",
"163Hint": "Opmerking: Gebruik voor 163 Mail een autorisatiecode, geen wachtwoord.",
"templateTitle": "2. E-mailjablonen",
"adminTemplate": "Beheerdersmelding (HTML)",
"adminTemplateHint": "Variabelen: ${commentAuthor}, ${postTitle}, ${postUrl}, ${commentContent}",
"replyTemplate": "Antwoordmelding (HTML)",
"replyTemplateHint": "Variabelen: ${toName}, ${replyAuthor}, ${postTitle}, ${postUrl}, ${parentComment}, ${replyContent}",
"testBtn": "Test e-mail verzenden",
"testingBtn": "Verzenden...",
"resetBtn": "Sjablonen resetten",
"save": "Configuratie opslaan",
"saving": "Opslaan..."
},
"telegramNotify": {
"title": "Telegram-instellingen",
"enable": "Telegram-meldingen inschakelen",
"botToken": "Bot Token",
"botTokenHint": "Maak een bot aan via @BotFather om een token te krijgen",
"chatId": "Chat ID",
"chatIdHint": "Gebruiker- of groeps-ID. Stuur een bericht naar de bot en gebruik API of @userinfobot om ID te vinden.",
"webhookBtn": "Webhook instellen",
"webhookSetting": "Instellen...",
"testBtn": "Testbericht verzenden",
"testingBtn": "Verzenden...",
"save": "Opslaan",
"saving": "Opslaan..."
},
"display": {
"title": "Weergave-instellingen",
"layoutTitle": "Titel beheerpaneel",
"layoutTitleHint": "Wordt weergegeven in de navigatiebalk.",
"adminLanguage": "Taal beheerpaneel",
"adminLanguageHint": "Taal van het beheersysteem.",
"save": "Opslaan",
"saving": "Opslaan..."
}
},
"common": {
"saveSuccess": "Opslaan geslaagd",
"saveFailed": "Opslaan mislukt",
"loading": "Laden..."
}
}

View File

@@ -0,0 +1,304 @@
{
"menu": {
"comments": "Gestão de Comentários",
"stats": "Painel",
"analytics": "Analítica",
"settings": "Configurações",
"data": "Dados"
},
"layout": {
"toggleMenu": "Alternar Menu",
"docs": "Documentação",
"logout": "Sair",
"moreActions": "Mais Ações",
"defaultSite": "Site Padrão",
"theme": {
"light": "Claro",
"dark": "Escuro",
"system": "Sistema"
},
"version": {
"title": "Info da Versão",
"apiAddress": "Endereço da API",
"apiVersion": "Versão da API",
"adminVersion": "Versão do Admin",
"match": "As versões do Admin e da API correspondem.",
"mismatch": "Versão incompatível. Atualize o serviço da API.",
"fetchError": "Falha ao obter versão:",
"loading": "Carregando...",
"notConfigured": "Não Configurado",
"notFetched": "Não Obtido",
"ok": "OK"
}
},
"stats": {
"title": "Painel",
"overview": "Visão Geral",
"total": "Total de Comentários",
"approved": "Aprovados",
"pending": "Pendentes",
"rejected": "Rejeitados",
"trend": "Tendência",
"bySite": "Por Site",
"last7Days": "Últimos 7 Dias",
"last30Days": "Últimos 30 Dias",
"noData": "Sem Dados",
"table": {
"domain": "Domínio",
"total": "Total",
"approved": "Aprovados",
"pending": "Pendentes",
"rejected": "Rejeitados"
},
"loadError": "Erro ao carregar estatísticas"
},
"analytics": {
"title": "Analítica",
"overview": "Visão Geral",
"totalPv": "PV Total",
"todayPv": "PV Hoje",
"weekPv": "PV Semana",
"monthPv": "PV Mês",
"totalPages": "Páginas Visitadas",
"trend": "Tendência de Tráfego",
"pageDetail": "Detalhes da Página",
"likeRank": "Top Curtidas",
"last7Days": "Últimos 7 Dias",
"last30Days": "Últimos 30 Dias",
"sort": {
"pv": "Ordenar por PV",
"latest": "Última Visita"
},
"table": {
"title": "Título da Página",
"pv": "PV",
"time": "Última Visita",
"url": "URL",
"rank": "Rank",
"like": "Curtidas"
},
"noData": "Sem Dados",
"noLikeData": "Sem Dados de Curtidas",
"loadError": "Erro ao carregar analítica"
},
"data": {
"title": "Gestão de Dados",
"sections": {
"comments": {
"title": "Dados de Comentários",
"desc": "Gerenciar e importar comentários.",
"exportLabel": "Exportar:",
"exporting": "Exportando...",
"exportJson": "Exportar JSON",
"importLabel": "Importar:",
"importButton": "Importar Comentários",
"source": {
"cwd": "CWD (.json)",
"twikoo": "Twikoo (.json)",
"artalk": "Artalk (.json)"
}
},
"config": {
"title": "Config do Sistema",
"desc": "Configurações, e-mail, lista negra, etc.",
"export": "Exportar Config",
"import": "Importar Config"
},
"stats": {
"title": "Dados Analíticos",
"desc": "Visualizações, curtidas e tendências.",
"export": "Exportar Stats",
"import": "Importar Stats"
},
"backup": {
"title": "Backup Completo",
"desc": "Backup ou restauração de tudo (Comentários + Config + Stats).",
"export": "Exportação Completa",
"import": "Restauração Completa"
}
},
"logs": {
"title": "Logs de Operação"
},
"prefixModal": {
"title": "Prefixo de URL Ausente",
"descPart1": "Detectado ",
"descPart2": " comentários sem prefixo de protocolo (http/https).",
"descPart3": "Adicionar ao importar?",
"label": "Prefixo de Domínio (ex: https://example.me)",
"placeholder": "Inserir prefixo",
"skip": "Pular",
"confirm": "Adicionar e Importar"
},
"messages": {
"exportSuccess": "Exportação com Sucesso",
"exportFailed": "Falha na Exportação",
"fileReadFailed": "Falha na Leitura do Arquivo",
"jsonParseFailed": "Falha na Análise JSON",
"importConfigSuccess": "Config Importada",
"importStatsSuccess": "Stats Importadas",
"importBackupSuccess": "Restauração Completa com Sucesso",
"importCommentsSuccess": "Comentários Importados",
"prefixRequired": "Por favor, insira o prefixo",
"importStart": "Importando: {name} (Modo: {mode})",
"fileParseSuccess": "Arquivo analisado, processando...",
"errorWithMessage": "Erro: {msg}",
"parsedCommentsCount": "{count} comentários analisados",
"detectMissingPrefix": "{count} URLs sem prefixo, aguardando confirmação...",
"prefixAdded": "Prefixo adicionado, continuando...",
"skipPrefix": "Adição de prefixo pulada",
"readFileFailedLog": "Falha na leitura do arquivo",
"importCommentsDone": "Importação Concluída: {message}"
}
},
"comments": {
"title": "Gestão de Comentários",
"statusFilter": {
"all": "Todos",
"approved": "Aprovados",
"pending": "Pendentes",
"rejected": "Rejeitados"
},
"refresh": "Atualizar",
"table": {
"author": "Usuário",
"content": "Comentário",
"path": "Caminho",
"status": "Status",
"actions": "Ações",
"admin": "Admin",
"time": "Hora",
"ip": "IP",
"email": "E-mail"
},
"actions": {
"approve": "Aprovar",
"pending": "Pendente",
"reject": "Rejeitar",
"edit": "Editar",
"delete": "Excluir",
"blockIp": "Bloquear IP",
"blockEmail": "Bloquear E-mail",
"pin": "Fixar"
},
"pagination": {
"prev": "Ant",
"next": "Próx",
"jumpTo": "Ir para",
"page": "Página",
"confirm": "OK"
},
"confirmDelete": "Excluir comentário {id}?",
"confirmBlockIp": "Bloquear IP {ip}?",
"confirmBlockEmail": "Bloquear e-mail {email}?",
"successBlockIp": "IP Bloqueado",
"successBlockEmail": "E-mail Bloqueado",
"empty": "Sem Dados",
"editModal": {
"title": "Editar Comentário",
"name": "Nome",
"email": "E-mail",
"url": "URL",
"postSlug": "Slug",
"postUrl": "URL do Post",
"content": "Conteúdo",
"status": "Status",
"priority": "Prioridade (1=Normal, maior=Topo)",
"cancel": "Cancelar",
"save": "Salvar",
"saving": "Salvando..."
}
},
"settings": {
"title": "Configurações",
"tabs": {
"comment": "Comentários e Segurança",
"feature": "Funcionalidades",
"emailNotify": "Notificação por E-mail",
"telegramNotify": "Notificação Telegram",
"display": "Exibição"
},
"comment": {
"title": "Exibição de Comentários",
"adminEmail": "E-mail Admin",
"adminBadge": "Texto do Emblema Admin (Vazio para ícone padrão)",
"adminEnabled": "Mostrar Emblema Admin",
"avatarPrefix": "Prefixo do Avatar (Padrão: https://gravatar.com/avatar)",
"securityTitle": "Segurança",
"requireReview": "Exigir Revisão para Novos Comentários",
"adminKey": "Chave Admin (Necessária para comentários com e-mail admin)",
"adminKeyPlaceholder": "Inserir chave",
"allowedDomains": "Domínios Permitidos (Vazio para todos. Separar por espaço/vírgula)",
"blockedIps": "IPs Bloqueados (Separar por vírgula/nova linha)",
"blockedEmails": "E-mails Bloqueados (Separar por vírgula/nova linha)",
"save": "Salvar",
"saving": "Salvando..."
},
"feature": {
"title": "Funcionalidades",
"articleLike": "Ativar Curtidas no Artigo",
"articleLikeHint": "Mostrar botão de curtir acima dos comentários.",
"commentLike": "Ativar Curtidas no Comentário",
"commentLikeHint": "Mostrar botão de curtir em cada comentário.",
"imageLightbox": "Lightbox de Imagem",
"imageLightboxHint": "Ativar visualização em tela cheia para imagens.",
"placeholder": "Texto do Placeholder",
"placeholderHint": "Placeholder personalizado para entrada de comentários.",
"widgetLanguage": "Idioma do Widget",
"widgetLanguageHint": "Idioma para o widget frontend.",
"save": "Salvar",
"saving": "Salvando..."
},
"emailNotify": {
"title": "Notificação por E-mail",
"enable": "Ativar Notificação por E-mail",
"smtpTitle": "1. Config SMTP",
"provider": "Provedor",
"host": "Host SMTP",
"port": "Porta SMTP",
"secure": "Segurança SSL",
"user": "Usuário de E-mail",
"pass": "Senha/Token de App",
"qqHint": "Nota: Para QQ Mail, use Token de App.",
"163Hint": "Nota: Para 163 Mail, use Token de App.",
"templateTitle": "2. Config de Modelos",
"adminTemplate": "Modelo Admin (HTML)",
"adminTemplateHint": "Vars: ${commentAuthor}, ${postTitle}, ${postUrl}, ${commentContent}",
"replyTemplate": "Modelo Resposta (HTML)",
"replyTemplateHint": "Vars: ${toName}, ${replyAuthor}, ${postTitle}, ${postUrl}, ${parentComment}, ${replyContent}",
"testBtn": "Enviar E-mail de Teste",
"testingBtn": "Enviando...",
"resetBtn": "Redefinir Modelos",
"save": "Salvar Config",
"saving": "Salvando..."
},
"telegramNotify": {
"title": "Notificação Telegram",
"enable": "Ativar Notificação Telegram",
"botToken": "Token do Bot",
"botTokenHint": "Obter via @BotFather",
"chatId": "ID do Chat",
"chatIdHint": "ID de Usuário ou Grupo. Use @userinfobot.",
"webhookBtn": "Definir Webhook",
"webhookSetting": "Configurando...",
"testBtn": "Enviar Mensagem de Teste",
"testingBtn": "Enviando...",
"save": "Salvar",
"saving": "Salvando..."
},
"display": {
"title": "Configurações de Exibição",
"layoutTitle": "Título do Admin",
"layoutTitleHint": "Exibido na barra de navegação superior.",
"adminLanguage": "Idioma do Admin",
"adminLanguageHint": "Idioma para o painel de administração.",
"save": "Salvar",
"saving": "Salvando..."
},
"common": {
"saveSuccess": "Salvo com Sucesso",
"saveFailed": "Falha ao Salvar",
"loading": "Carregando..."
}
}
}

View File

@@ -0,0 +1,304 @@
{
"menu": {
"comments": "Управление Комментариями",
"stats": "Панель",
"analytics": "Аналитика",
"settings": "Настройки",
"data": "Данные"
},
"layout": {
"toggleMenu": "Меню",
"docs": "Документация",
"logout": "Выйти",
"moreActions": "Ещё",
"defaultSite": "Сайт по Умолчанию",
"theme": {
"light": "Светлая",
"dark": "Тёмная",
"system": "Системная"
},
"version": {
"title": "Версия",
"apiAddress": "Адрес API",
"apiVersion": "Версия API",
"adminVersion": "Версия Админки",
"match": "Версии совпадают.",
"mismatch": "Версии не совпадают. Обновите API.",
"fetchError": "Ошибка получения версии:",
"loading": "Загрузка...",
"notConfigured": "Не настроено",
"notFetched": "Не получено",
"ok": "ОК"
}
},
"stats": {
"title": "Панель",
"overview": "Обзор",
"total": "Всего Комментариев",
"approved": "Одобрено",
"pending": "На проверке",
"rejected": "Отклонено",
"trend": "Тренд",
"bySite": "По сайтам",
"last7Days": "7 дней",
"last30Days": "30 дней",
"noData": "Нет данных",
"table": {
"domain": "Домен",
"total": "Всего",
"approved": "Одобрено",
"pending": "На проверке",
"rejected": "Отклонено"
},
"loadError": "Ошибка загрузки статистики"
},
"analytics": {
"title": "Аналитика",
"overview": "Обзор",
"totalPv": "Всего PV",
"todayPv": "PV Сегодня",
"weekPv": "PV Неделя",
"monthPv": "PV Месяц",
"totalPages": "Посещённые страницы",
"trend": "Тренд Трафика",
"pageDetail": "Детали Страницы",
"likeRank": "Топ Лайков",
"last7Days": "7 дней",
"last30Days": "30 дней",
"sort": {
"pv": "Сорт. по PV",
"latest": "Последние"
},
"table": {
"title": "Заголовок",
"pv": "PV",
"time": "Последний Визит",
"url": "URL",
"rank": "Ранг",
"like": "Лайки"
},
"noData": "Нет данных",
"noLikeData": "Нет данных о лайках",
"loadError": "Ошибка загрузки аналитики"
},
"data": {
"title": "Управление Данными",
"sections": {
"comments": {
"title": "Данные Комментариев",
"desc": "Управление и импорт комментариев.",
"exportLabel": "Экспорт:",
"exporting": "Экспорт...",
"exportJson": "Экспорт JSON",
"importLabel": "Импорт:",
"importButton": "Импорт Комментариев",
"source": {
"cwd": "CWD (.json)",
"twikoo": "Twikoo (.json)",
"artalk": "Artalk (.json)"
}
},
"config": {
"title": "Конфигурация",
"desc": "Настройки, email, чёрный список.",
"export": "Экспорт Конфига",
"import": "Импорт Конфига"
},
"stats": {
"title": "Аналитика",
"desc": "Просмотры, лайки, тренды.",
"export": "Экспорт Статистики",
"import": "Импорт Статистики"
},
"backup": {
"title": "Полный Бэкап",
"desc": "Бэкап или восстановление всего (Комментарии + Конфиг + Статистика).",
"export": "Полный Экспорт",
"import": "Полное Восстановление"
}
},
"logs": {
"title": "Журнал Операций"
},
"prefixModal": {
"title": "Отсутствует префикс URL",
"descPart1": "Обнаружено ",
"descPart2": " комментариев без префикса (http/https).",
"descPart3": "Добавить при импорте?",
"label": "Префикс домена (напр. https://example.me)",
"placeholder": "Введите префикс",
"skip": "Пропустить",
"confirm": "Добавить и Импортировать"
},
"messages": {
"exportSuccess": "Экспорт успешен",
"exportFailed": "Ошибка экспорта",
"fileReadFailed": "Ошибка чтения файла",
"jsonParseFailed": "Ошибка парсинга JSON",
"importConfigSuccess": "Конфиг импортирован",
"importStatsSuccess": "Статистика импортирована",
"importBackupSuccess": "Восстановление успешно",
"importCommentsSuccess": "Комментарии импортированы",
"prefixRequired": "Введите префикс",
"importStart": "Импорт: {name} (Режим: {mode})",
"fileParseSuccess": "Файл разобран, обработка...",
"errorWithMessage": "Ошибка: {msg}",
"parsedCommentsCount": "Разобрано {count} комментариев",
"detectMissingPrefix": "{count} URL без префикса, ожидание...",
"prefixAdded": "Префикс добавлен, продолжение...",
"skipPrefix": "Добавление префикса пропущено",
"readFileFailedLog": "Ошибка чтения файла",
"importCommentsDone": "Импорт завершён: {message}"
}
},
"comments": {
"title": "Управление Комментариями",
"statusFilter": {
"all": "Все",
"approved": "Одобрено",
"pending": "На проверке",
"rejected": "Отклонено"
},
"refresh": "Обновить",
"table": {
"author": "Пользователь",
"content": "Комментарий",
"path": "Путь",
"status": "Статус",
"actions": "Действия",
"admin": "Админ",
"time": "Время",
"ip": "IP",
"email": "Email"
},
"actions": {
"approve": "Одобрить",
"pending": "На проверку",
"reject": "Отклонить",
"edit": "Редактировать",
"delete": "Удалить",
"blockIp": "Блок IP",
"blockEmail": "Блок Email",
"pin": "Закрепить"
},
"pagination": {
"prev": "Пред",
"next": "След",
"jumpTo": "Перейти",
"page": "Стр",
"confirm": "ОК"
},
"confirmDelete": "Удалить комментарий {id}?",
"confirmBlockIp": "Блокировать IP {ip}?",
"confirmBlockEmail": "Блокировать Email {email}?",
"successBlockIp": "IP заблокирован",
"successBlockEmail": "Email заблокирован",
"empty": "Нет данных",
"editModal": {
"title": "Редактировать Комментарий",
"name": "Имя",
"email": "Email",
"url": "URL",
"postSlug": "Слаг",
"postUrl": "URL Поста",
"content": "Контент",
"status": "Статус",
"priority": "Приоритет (1=Обычный, выше=Топ)",
"cancel": "Отмена",
"save": "Сохранить",
"saving": "Сохранение..."
}
},
"settings": {
"title": "Настройки",
"tabs": {
"comment": "Комментарии и Безопасность",
"feature": "Функции",
"emailNotify": "Email Уведомления",
"telegramNotify": "Telegram Уведомления",
"display": "Отображение"
},
"comment": {
"title": "Отображение Комментариев",
"adminEmail": "Email Админа",
"adminBadge": "Текст Бейджа Админа (Пусто для иконки)",
"adminEnabled": "Показывать Бейдж Админа",
"avatarPrefix": "Префикс Аватара (По умолч.: https://gravatar.com/avatar)",
"securityTitle": "Безопасность",
"requireReview": "Премодерация Новых Комментариев",
"adminKey": "Ключ Админа (Для комм. с email админа)",
"adminKeyPlaceholder": "Введите ключ",
"allowedDomains": "Разрешённые Домены (Пусто для всех. Разделитель пробел/запятая)",
"blockedIps": "Заблокированные IP (Разделитель запятая/новая строка)",
"blockedEmails": "Заблокированные Email (Разделитель запятая/новая строка)",
"save": "Сохранить",
"saving": "Сохранение..."
},
"feature": {
"title": "Функции",
"articleLike": "Включить Лайки Статей",
"articleLikeHint": "Кнопка лайка над комментариями.",
"commentLike": "Включить Лайки Комментариев",
"commentLikeHint": "Кнопка лайка у каждого комментария.",
"imageLightbox": "Лайтбокс Изображений",
"imageLightboxHint": "Полноэкранный просмотр изображений.",
"placeholder": "Текст Плейсхолдера",
"placeholderHint": "Текст подсказки в поле ввода.",
"widgetLanguage": "Язык Виджета",
"widgetLanguageHint": "Язык фронтенд виджета.",
"save": "Сохранить",
"saving": "Сохранение..."
},
"emailNotify": {
"title": "Email Уведомления",
"enable": "Включить Email Уведомления",
"smtpTitle": "1. Настройки SMTP",
"provider": "Провайдер",
"host": "SMTP Хост",
"port": "SMTP Порт",
"secure": "SSL Безопасность",
"user": "Email Пользователь",
"pass": "Пароль/Токен",
"qqHint": "Для QQ Mail используйте токен приложения.",
"163Hint": "Для 163 Mail используйте токен приложения.",
"templateTitle": "2. Настройки Шаблонов",
"adminTemplate": "Шаблон Админа (HTML)",
"adminTemplateHint": "Перем: ${commentAuthor}, ${postTitle}, ${postUrl}, ${commentContent}",
"replyTemplate": "Шаблон Ответа (HTML)",
"replyTemplateHint": "Перем: ${toName}, ${replyAuthor}, ${postTitle}, ${postUrl}, ${parentComment}, ${replyContent}",
"testBtn": "Отправить Тест",
"testingBtn": "Отправка...",
"resetBtn": "Сбросить Шаблоны",
"save": "Сохранить Конфиг",
"saving": "Сохранение..."
},
"telegramNotify": {
"title": "Telegram Уведомления",
"enable": "Включить Telegram Уведомления",
"botToken": "Токен Бота",
"botTokenHint": "Получить у @BotFather",
"chatId": "ID Чата",
"chatIdHint": "ID Пользователя или Группы. Исп. @userinfobot.",
"webhookBtn": "Установить Вебхук",
"webhookSetting": "Настройка...",
"testBtn": "Отправить Тест",
"testingBtn": "Отправка...",
"save": "Сохранить",
"saving": "Сохранение..."
},
"display": {
"title": "Настройки Отображения",
"layoutTitle": "Заголовок Админки",
"layoutTitleHint": "Отображается в верхней панели.",
"adminLanguage": "Язык Админки",
"adminLanguageHint": "Язык панели администратора.",
"save": "Сохранить",
"saving": "Сохранение..."
},
"common": {
"saveSuccess": "Сохранено",
"saveFailed": "Ошибка Сохранения",
"loading": "Загрузка..."
}
}
}

View File

@@ -0,0 +1,304 @@
{
"menu": {
"comments": "评论管理",
"stats": "数据看板",
"analytics": "访问统计",
"settings": "网站设置",
"data": "数据管理"
},
"layout": {
"toggleMenu": "切换菜单",
"docs": "使用文档",
"logout": "退出",
"moreActions": "更多操作",
"defaultSite": "默认站点",
"theme": {
"light": "明亮模式",
"dark": "暗黑模式",
"system": "跟随系统"
},
"version": {
"title": "版本信息",
"apiAddress": "API 地址",
"apiVersion": "接口版本",
"adminVersion": "后台版本",
"match": "当前后台与接口版本一致,可以正常使用。",
"mismatch": "当前后台与接口版本不一致,推荐将 API 服务更新到与后台版本一致,以避免潜在的兼容性问题。",
"fetchError": "无法获取接口版本:",
"loading": "加载中...",
"notConfigured": "未配置",
"notFetched": "未获取到",
"ok": "我知道了"
}
},
"stats": {
"title": "数据看板",
"overview": "整体概览",
"total": "总评论数",
"approved": "已通过",
"pending": "待审核",
"rejected": "已拒绝",
"trend": "评论数趋势",
"bySite": "按站点统计",
"last7Days": "最近 7 天",
"last30Days": "最近 30 天",
"noData": "暂无评论数据",
"table": {
"domain": "域名",
"total": "总数",
"approved": "已通过",
"pending": "待审核",
"rejected": "已拒绝"
},
"loadError": "加载统计数据失败"
},
"analytics": {
"title": "访问统计",
"overview": "整体概览",
"totalPv": "全站总访问量",
"todayPv": "今日访问量",
"weekPv": "本周访问量",
"monthPv": "本月访问量",
"totalPages": "有访问记录的页面数",
"trend": "访问趋势",
"pageDetail": "页面访问明细",
"likeRank": "点赞页面排行榜",
"last7Days": "最近 7 天",
"last30Days": "最近 30 天",
"sort": {
"pv": "按 PV 排序",
"latest": "最新访问"
},
"table": {
"title": "页面标题",
"pv": "访问量",
"time": "最后访问时间",
"url": "页面地址",
"rank": "排名",
"like": "点赞数"
},
"noData": "暂无访问数据",
"noLikeData": "暂无点赞数据",
"loadError": "加载访问统计数据失败"
},
"data": {
"title": "数据管理",
"sections": {
"comments": {
"title": "评论数据",
"desc": "管理评论内容,支持从其他评论框架迁移数据。",
"exportLabel": "导出:",
"exporting": "导出中...",
"exportJson": "导出 JSON",
"importLabel": "导入:",
"importButton": "导入评论",
"source": {
"cwd": "CWD (.json)",
"twikoo": "Twikoo (.json)",
"artalk": "Artalk (.json)"
}
},
"config": {
"title": "系统配置",
"desc": "管理后台设置、邮件配置、黑名单等。",
"export": "导出配置",
"import": "导入配置"
},
"stats": {
"title": "访问统计",
"desc": "管理文章访问量、点赞数及每日访问趋势。",
"export": "导出统计",
"import": "导入统计"
},
"backup": {
"title": "全量备份",
"desc": "一键备份或恢复系统所有数据(评论 + 配置 + 统计)。",
"export": "全量导出",
"import": "全量恢复"
}
},
"logs": {
"title": "操作日志"
},
"prefixModal": {
"title": "检测到 URL 缺失前缀",
"descPart1": "检测到 ",
"descPart2": " 条评论的 URL 不存在域名前缀http/https。",
"descPart3": "是否在导入时统一添加?",
"label": "域名前缀 (例如 https://example.me)",
"placeholder": "请输入域名前缀",
"skip": "直接导入 (不添加)",
"confirm": "添加并导入"
},
"messages": {
"exportSuccess": "导出成功",
"exportFailed": "导出失败",
"fileReadFailed": "读取文件失败",
"jsonParseFailed": "JSON 解析失败,请检查文件格式",
"importConfigSuccess": "配置导入成功",
"importStatsSuccess": "统计数据导入成功",
"importBackupSuccess": "全量恢复成功",
"importCommentsSuccess": "评论导入成功",
"prefixRequired": "请输入域名前缀",
"importStart": "开始导入: {name} (模式: {mode})",
"fileParseSuccess": "文件解析成功,开始处理...",
"errorWithMessage": "错误: {msg}",
"parsedCommentsCount": "解析到 {count} 条评论数据",
"detectMissingPrefix": "检测到 {count} 条 URL 缺失前缀,等待用户确认...",
"prefixAdded": "已添加前缀,继续导入...",
"skipPrefix": "用户跳过前缀添加",
"readFileFailedLog": "读取文件失败",
"importCommentsDone": "导入完成: {message}"
}
},
"comments": {
"title": "评论管理",
"statusFilter": {
"all": "全部状态",
"approved": "已通过",
"pending": "待审核",
"rejected": "已拒绝"
},
"refresh": "刷新",
"table": {
"author": "用户",
"content": "评论信息",
"path": "评论地址",
"status": "状态",
"actions": "操作",
"admin": "网站管理员",
"time": "时间",
"ip": "IP",
"email": "邮箱"
},
"actions": {
"approve": "通过",
"pending": "待审",
"reject": "拒绝",
"edit": "编辑",
"delete": "删除",
"blockIp": "屏蔽该 IP",
"blockEmail": "屏蔽该邮箱",
"pin": "置顶"
},
"pagination": {
"prev": "上一页",
"next": "下一页",
"jumpTo": "跳转到",
"page": "页",
"confirm": "确定"
},
"confirmDelete": "确认删除评论 {id} 吗",
"confirmBlockIp": "确认将 IP {ip} 加入黑名单吗?",
"confirmBlockEmail": "确认将邮箱 {email} 加入黑名单吗?",
"successBlockIp": "已加入 IP 黑名单",
"successBlockEmail": "已加入邮箱黑名单",
"empty": "暂无数据",
"editModal": {
"title": "编辑评论",
"name": "访客昵称",
"email": "访客邮箱",
"url": "访客网址",
"postSlug": "页面标识",
"postUrl": "评论地址",
"content": "评论内容",
"status": "评论状态",
"priority": "置顶权重1 为不置顶,数值越大越靠前)",
"cancel": "取消",
"save": "保存",
"saving": "保存中..."
}
},
"settings": {
"title": "网站设置",
"tabs": {
"comment": "评论与安全",
"feature": "功能开关",
"emailNotify": "邮箱提醒",
"telegramNotify": "Telegram 通知",
"display": "后台显示"
},
"comment": {
"title": "评论显示配置",
"adminEmail": "管理员邮箱",
"adminBadge": "博主标签文字(留空使用默认图标)",
"adminEnabled": "是否开启博主标签显示",
"avatarPrefix": "头像前缀默认https://gravatar.com/avatar",
"securityTitle": "安全设置",
"requireReview": "新评论是否审核后再显示",
"adminKey": "管理员评论密钥(设置后前台使用管理员邮箱评论需输入此密钥。)",
"adminKeyPlaceholder": "输入密钥以设置或修改",
"allowedDomains": "允许调用的域名(设置后仅匹配域名可调用前台评论组件,留空则不限制。使用空格或者逗号进行分割。)",
"blockedIps": "IP 黑名单(多个 IP 用逗号或换行分隔,留空则不限制)",
"blockedEmails": "邮箱黑名单(多个邮箱用逗号或换行分隔,留空则不限制)",
"save": "保存",
"saving": "保存中..."
},
"feature": {
"title": "功能开关",
"articleLike": "开启文章点赞功能",
"articleLikeHint": "开启后,评论区顶部会显示的文章点赞(喜欢)按钮。",
"commentLike": "开启评论点赞功能",
"commentLikeHint": "开启后,评论列表中的每条评论都会显示点赞按钮。",
"imageLightbox": "评论列表图片灯箱模式",
"imageLightboxHint": "开启后,点击评论中的图片时会全屏放大预览。",
"placeholder": "评论框提示文案 (Placeholder)",
"placeholderHint": "自定义评论输入框的提示文字,支持换行。留空则使用默认值。",
"widgetLanguage": "组件语言",
"widgetLanguageHint": "设置前端评论组件显示的语言。",
"save": "保存",
"saving": "保存中..."
},
"emailNotify": {
"title": "通知邮箱设置",
"enable": "开启邮件通知",
"smtpTitle": "1. SMTP 发件配置",
"provider": "邮件服务商",
"host": "SMTP 服务器",
"port": "SMTP 端口",
"secure": "SSL 安全连接",
"user": "发件邮箱账号",
"pass": "授权码/密码",
"qqHint": "注意QQ 邮箱必须使用授权码,而非 QQ 密码。请登录 QQ 邮箱网页版,在【设置 - 账户】中开启 POP3/SMTP 服务并生成授权码。",
"163Hint": "注意163 邮箱必须使用授权码,而非登录密码。请登录 163 邮箱网页版,在【设置 - POP3/SMTP/IMAP】中开启服务并生成授权码。",
"templateTitle": "2. 邮件模板设置",
"adminTemplate": "管理员通知模板 (HTML)",
"adminTemplateHint": "可用变量:${commentAuthor} (评论人昵称), ${postTitle} (文章标题), ${postUrl} (文章链接), ${commentContent} (评论内容)",
"replyTemplate": "回复通知模板 (HTML)",
"replyTemplateHint": "可用变量:${toName} (接收人昵称), ${replyAuthor} (回复人昵称), ${postTitle} (文章标题), ${postUrl} (文章链接), ${parentComment} (原评论), ${replyContent} (回复内容)",
"testBtn": "发送测试邮件",
"testingBtn": "发送中...",
"resetBtn": "恢复默认模板",
"save": "保存配置",
"saving": "保存中..."
},
"telegramNotify": {
"title": "Telegram 通知设置",
"enable": "开启 Telegram 通知",
"botToken": "Bot Token",
"botTokenHint": "在 Telegram 中搜索 @BotFather 创建机器人获取 Token",
"chatId": "Chat ID",
"chatIdHint": "这是接收通知的用户 ID 或群组 ID。可以先给机器人发消息然后通过 API 获取 ID或者使用 @userinfobot 查询。",
"webhookBtn": "一键设置 Webhook",
"webhookSetting": "设置中...",
"testBtn": "发送测试消息",
"testingBtn": "发送中...",
"save": "保存",
"saving": "保存中..."
},
"display": {
"title": "后台显示设置",
"layoutTitle": "后台标题",
"layoutTitleHint": "显示在后台顶部导航栏左侧。",
"adminLanguage": "后台语言",
"adminLanguageHint": "设置后台管理系统的显示语言。",
"save": "保存",
"saving": "保存中..."
}
},
"common": {
"saveSuccess": "保存成功",
"saveFailed": "保存失败",
"loading": "加载中..."
}
}

View File

@@ -0,0 +1,304 @@
{
"menu": {
"comments": "評論管理",
"stats": "數據看板",
"analytics": "訪問統計",
"settings": "網站設置",
"data": "數據管理"
},
"layout": {
"toggleMenu": "切換菜單",
"docs": "使用文檔",
"logout": "退出",
"moreActions": "更多操作",
"defaultSite": "默認站點",
"theme": {
"light": "明亮模式",
"dark": "暗黑模式",
"system": "跟隨系統"
},
"version": {
"title": "版本信息",
"apiAddress": "API 地址",
"apiVersion": "接口版本",
"adminVersion": "後台版本",
"match": "當前後台與接口版本一致,可以正常使用。",
"mismatch": "當前後台與接口版本不一致,推薦將 API 服務更新到與後台版本一致,以避免潛在的兼容性問題。",
"fetchError": "無法獲取接口版本:",
"loading": "加載中...",
"notConfigured": "未配置",
"notFetched": "未獲取到",
"ok": "我知道了"
}
},
"stats": {
"title": "數據看板",
"overview": "整體概覽",
"total": "總評論數",
"approved": "已通過",
"pending": "待審核",
"rejected": "已拒絕",
"trend": "評論數趨勢",
"bySite": "按站點統計",
"last7Days": "最近 7 天",
"last30Days": "最近 30 天",
"noData": "暫無評論數據",
"table": {
"domain": "域名",
"total": "總數",
"approved": "已通過",
"pending": "待審核",
"rejected": "已拒絕"
},
"loadError": "加載統計數據失敗"
},
"analytics": {
"title": "訪問統計",
"overview": "整體概覽",
"totalPv": "全站總訪問量",
"todayPv": "今日訪問量",
"weekPv": "本週訪問量",
"monthPv": "本月訪問量",
"totalPages": "有訪問記錄的頁面數",
"trend": "訪問趨勢",
"pageDetail": "頁面訪問明細",
"likeRank": "點贊頁面排行榜",
"last7Days": "最近 7 天",
"last30Days": "最近 30 天",
"sort": {
"pv": "按 PV 排序",
"latest": "最新訪問"
},
"table": {
"title": "頁面標題",
"pv": "訪問量",
"time": "最後訪問時間",
"url": "頁面地址",
"rank": "排名",
"like": "點贊數"
},
"noData": "暫無訪問數據",
"noLikeData": "暫無點贊數據",
"loadError": "加載訪問統計數據失敗"
},
"data": {
"title": "數據管理",
"sections": {
"comments": {
"title": "評論數據",
"desc": "管理評論內容,支持從其他評論框架遷移數據。",
"exportLabel": "導出:",
"exporting": "導出中...",
"exportJson": "導出 JSON",
"importLabel": "導入:",
"importButton": "導入評論",
"source": {
"cwd": "CWD (.json)",
"twikoo": "Twikoo (.json)",
"artalk": "Artalk (.json)"
}
},
"config": {
"title": "系統配置",
"desc": "管理後台設置、郵件配置、黑名單等。",
"export": "導出配置",
"import": "導入配置"
},
"stats": {
"title": "訪問統計",
"desc": "管理文章訪問量、點贊數及每日訪問趨勢。",
"export": "導出統計",
"import": "導入統計"
},
"backup": {
"title": "全量備份",
"desc": "一鍵備份或恢復系統所有數據(評論 + 配置 + 統計)。",
"export": "全量導出",
"import": "全量恢復"
}
},
"logs": {
"title": "操作日誌"
},
"prefixModal": {
"title": "檢測到 URL 缺失前綴",
"descPart1": "檢測到 ",
"descPart2": " 條評論的 URL 不存在域名前綴http/https。",
"descPart3": "是否在導入時統一添加?",
"label": "域名前綴 (例如 https://example.me)",
"placeholder": "請輸入域名前綴",
"skip": "直接導入 (不添加)",
"confirm": "添加並導入"
},
"messages": {
"exportSuccess": "導出成功",
"exportFailed": "導出失敗",
"fileReadFailed": "讀取文件失敗",
"jsonParseFailed": "JSON 解析失敗,請檢查文件格式",
"importConfigSuccess": "配置導入成功",
"importStatsSuccess": "統計數據導入成功",
"importBackupSuccess": "全量恢復成功",
"importCommentsSuccess": "評論導入成功",
"prefixRequired": "請輸入域名前綴",
"importStart": "開始導入: {name} (模式: {mode})",
"fileParseSuccess": "文件解析成功,開始處理...",
"errorWithMessage": "錯誤: {msg}",
"parsedCommentsCount": "解析到 {count} 條評論數據",
"detectMissingPrefix": "檢測到 {count} 條 URL 缺失前綴,等待用戶確認...",
"prefixAdded": "已添加前綴,繼續導入...",
"skipPrefix": "用戶跳過前綴添加",
"readFileFailedLog": "讀取文件失敗",
"importCommentsDone": "導入完成: {message}"
}
},
"comments": {
"title": "評論管理",
"statusFilter": {
"all": "全部狀態",
"approved": "已通過",
"pending": "待審核",
"rejected": "已拒絕"
},
"refresh": "刷新",
"table": {
"author": "用戶",
"content": "評論信息",
"path": "評論地址",
"status": "狀態",
"actions": "操作",
"admin": "網站管理員",
"time": "時間",
"ip": "IP",
"email": "郵箱"
},
"actions": {
"approve": "通過",
"pending": "待審",
"reject": "拒絕",
"edit": "編輯",
"delete": "刪除",
"blockIp": "屏蔽該 IP",
"blockEmail": "屏蔽該郵箱",
"pin": "置頂"
},
"pagination": {
"prev": "上一頁",
"next": "下一頁",
"jumpTo": "跳轉到",
"page": "頁",
"confirm": "確定"
},
"confirmDelete": "確認刪除評論 {id} 嗎",
"confirmBlockIp": "確認將 IP {ip} 加入黑名單嗎?",
"confirmBlockEmail": "確認將郵箱 {email} 加入黑名單嗎?",
"successBlockIp": "已加入 IP 黑名單",
"successBlockEmail": "已加入郵箱黑名單",
"empty": "暫無數據",
"editModal": {
"title": "編輯評論",
"name": "訪客暱稱",
"email": "訪客郵箱",
"url": "訪客網址",
"postSlug": "頁面標識",
"postUrl": "評論地址",
"content": "評論內容",
"status": "評論狀態",
"priority": "置頂權重1 為不置頂,數值越大越靠前)",
"cancel": "取消",
"save": "保存",
"saving": "保存中..."
}
},
"settings": {
"title": "網站設置",
"tabs": {
"comment": "評論與安全",
"feature": "功能開關",
"emailNotify": "郵箱提醒",
"telegramNotify": "Telegram 通知",
"display": "後台顯示"
},
"comment": {
"title": "評論顯示配置",
"adminEmail": "管理員郵箱",
"adminBadge": "博主標籤文字(留空使用默認圖標)",
"adminEnabled": "是否開啟博主標籤顯示",
"avatarPrefix": "頭像前綴默認https://gravatar.com/avatar",
"securityTitle": "安全設置",
"requireReview": "新評論是否審核後再顯示",
"adminKey": "管理員評論密鑰(設置後前台使用管理員郵箱評論需輸入此密鑰。)",
"adminKeyPlaceholder": "輸入密鑰以設置或修改",
"allowedDomains": "允許調用的域名(設置後僅匹配域名可調用前台評論組件,留空則不限制。使用空格或者逗號進行分割。)",
"blockedIps": "IP 黑名單(多個 IP 用逗號或換行分隔,留空則不限制)",
"blockedEmails": "郵箱黑名單(多個郵箱用逗號或換行分隔,留空則不限制)",
"save": "保存",
"saving": "保存中..."
},
"feature": {
"title": "功能開關",
"articleLike": "開啟文章點贊功能",
"articleLikeHint": "開啟後,評論區頂部會顯示的文章點贊(喜歡)按鈕。",
"commentLike": "開啟評論點贊功能",
"commentLikeHint": "開啟後,評論列表中的每條評論都會顯示點贊按鈕。",
"imageLightbox": "評論列表圖片灯箱模式",
"imageLightboxHint": "開啟後,點擊評論中的圖片時會全屏放大預覽。",
"placeholder": "評論框提示文案 (Placeholder)",
"placeholderHint": "自定義評論輸入框的提示文字,支持換行。留空則使用默認值。",
"widgetLanguage": "組件語言",
"widgetLanguageHint": "設置前端評論組件顯示的語言。",
"save": "保存",
"saving": "保存中..."
},
"emailNotify": {
"title": "通知郵箱設置",
"enable": "開啟郵件通知",
"smtpTitle": "1. SMTP 發件配置",
"provider": "郵件服務商",
"host": "SMTP 服務器",
"port": "SMTP 端口",
"secure": "SSL 安全連接",
"user": "發件郵箱賬號",
"pass": "授權碼/密碼",
"qqHint": "注意QQ 郵箱必須使用授權碼,而非 QQ 密碼。請登錄 QQ 郵箱網頁版,在【設置 - 賬戶】中開啟 POP3/SMTP 服務並生成授權碼。",
"163Hint": "注意163 郵箱必須使用授權碼,而非登錄密碼。請登錄 163 郵箱網頁版,在【設置 - POP3/SMTP/IMAP】中開啟服務並生成授權碼。",
"templateTitle": "2. 郵件模板設置",
"adminTemplate": "管理員通知模板 (HTML)",
"adminTemplateHint": "可用變量:${commentAuthor} (評論人暱稱), ${postTitle} (文章標題), ${postUrl} (文章鏈接), ${commentContent} (評論內容)",
"replyTemplate": "回覆通知模板 (HTML)",
"replyTemplateHint": "可用變量:${toName} (接收人暱稱), ${replyAuthor} (回覆人暱稱), ${postTitle} (文章標題), ${postUrl} (文章鏈接), ${parentComment} (原評論), ${replyContent} (回覆內容)",
"testBtn": "發送測試郵件",
"testingBtn": "發送中...",
"resetBtn": "恢復默認模板",
"save": "保存配置",
"saving": "保存中..."
},
"telegramNotify": {
"title": "Telegram 通知設置",
"enable": "開啟 Telegram 通知",
"botToken": "Bot Token",
"botTokenHint": "在 Telegram 中搜索 @BotFather 創建機器人獲取 Token",
"chatId": "Chat ID",
"chatIdHint": "這是接收通知的用戶 ID 或群組 ID。可以先給機器人發消息然後通過 API 獲取 ID或者使用 @userinfobot 查詢。",
"webhookBtn": "一鍵設置 Webhook",
"webhookSetting": "設置中...",
"testBtn": "發送測試消息",
"testingBtn": "發送中...",
"save": "保存",
"saving": "保存中..."
},
"display": {
"title": "後台顯示設置",
"layoutTitle": "後台標題",
"layoutTitleHint": "顯示在後台頂部導航欄左側。",
"adminLanguage": "後台語言",
"adminLanguageHint": "設置後台管理系統的顯示語言。",
"save": "保存",
"saving": "保存中..."
},
"common": {
"saveSuccess": "保存成功",
"saveFailed": "保存失敗",
"loading": "加載中..."
}
}
}

View File

@@ -1,6 +1,7 @@
import { createApp } from 'vue';
import App from './App.vue';
import { router } from './router';
import i18n from './i18n';
import PhosphorIcons from '@phosphor-icons/vue';
@@ -9,5 +10,6 @@ import './styles/common.less';
const app = createApp(App);
app.use(router);
app.use(i18n);
app.use(PhosphorIcons);
app.mount('#app');

View File

@@ -157,6 +157,12 @@
gap: 8px;
}
.menu-item span {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.menu-item-icon {
flex: 0 0 auto;
}
@@ -307,4 +313,4 @@
.layout-actions-item-danger:hover {
background-color: rgba(209, 36, 47, 0.1);
}
}
}

View File

@@ -1,7 +1,7 @@
<template>
<div class="page">
<div style="display: flex; align-items: center; gap: 20px">
<h2 class="page-title">访问统计</h2>
<h2 class="page-title">{{ t('analytics.title') }}</h2>
</div>
<div
v-if="toastVisible"
@@ -13,18 +13,18 @@
<div class="card">
<div class="card-title-row">
<h3 class="card-title">整体概览</h3>
<h3 class="card-title">{{ t('analytics.overview') }}</h3>
</div>
<div v-if="loading" class="page-hint">加载中...</div>
<div v-if="loading" class="page-hint">{{ t('common.loading') }}</div>
<div v-else-if="error" class="page-error">{{ error }}</div>
<div v-else>
<div class="stats-grid">
<div class="stats-item">
<div class="stats-label">全站总访问量</div>
<div class="stats-label">{{ t('analytics.totalPv') }}</div>
<div class="stats-value"><CountTo :end-val="overview.totalPv" /></div>
</div>
<div class="stats-item">
<div class="stats-label">今日访问量</div>
<div class="stats-label">{{ t('analytics.todayPv') }}</div>
<div class="stats-value">
<CountTo :end-val="overview.todayPv" />
<span
@@ -39,7 +39,7 @@
</div>
</div>
<div class="stats-item">
<div class="stats-label">本周访问量</div>
<div class="stats-label">{{ t('analytics.weekPv') }}</div>
<div class="stats-value">
<CountTo :end-val="overview.weekPv" />
<span
@@ -54,7 +54,7 @@
</div>
</div>
<div class="stats-item">
<div class="stats-label">本月访问量</div>
<div class="stats-label">{{ t('analytics.monthPv') }}</div>
<div class="stats-value">
<CountTo :end-val="overview.monthPv" />
<span
@@ -69,7 +69,7 @@
</div>
</div>
<div class="stats-item">
<div class="stats-label">有访问记录的页面数</div>
<div class="stats-label">{{ t('analytics.totalPages') }}</div>
<div class="stats-value"><CountTo :end-val="overview.totalPages" /></div>
</div>
</div>
@@ -78,7 +78,7 @@
<div class="card">
<div class="card-title-row">
<h3 class="card-title">访问趋势</h3>
<h3 class="card-title">{{ t('analytics.trend') }}</h3>
<div class="visit-tabs">
<button
class="visit-tab"
@@ -86,7 +86,7 @@
type="button"
@click="changeChartRange('7')"
>
最近 7
{{ t('analytics.last7Days') }}
</button>
<button
class="visit-tab"
@@ -94,11 +94,11 @@
type="button"
@click="changeChartRange('30')"
>
最近 30
{{ t('analytics.last30Days') }}
</button>
</div>
</div>
<div v-if="loading" class="page-hint">加载中...</div>
<div v-if="loading" class="page-hint">{{ t('common.loading') }}</div>
<div v-else-if="error" class="page-error">{{ error }}</div>
<div class="chart-wrapper">
<div ref="chartEl" class="chart"></div>
@@ -107,7 +107,7 @@
<div class="card">
<div class="card-title-row">
<h3 class="card-title">页面访问明细</h3>
<h3 class="card-title">{{ t('analytics.pageDetail') }}</h3>
<div class="visit-tabs">
<button
class="visit-tab"
@@ -115,7 +115,7 @@
type="button"
@click="changeVisitTab('pv')"
>
PV 排序
{{ t('analytics.sort.pv') }}
</button>
<button
class="visit-tab"
@@ -123,20 +123,20 @@
type="button"
@click="changeVisitTab('latest')"
>
最新访问
{{ t('analytics.sort.latest') }}
</button>
</div>
</div>
<div v-if="listLoading" class="page-hint">加载中...</div>
<div v-if="listLoading" class="page-hint">{{ t('common.loading') }}</div>
<div v-else-if="error" class="page-error">{{ error }}</div>
<div v-else-if="items.length === 0" class="page-hint">暂无访问数据</div>
<div v-else-if="items.length === 0" class="page-hint">{{ t('analytics.noData') }}</div>
<div v-else class="domain-table-wrapper">
<div class="domain-table">
<div class="domain-table-header">
<div class="domain-cell domain-cell-title">页面标题</div>
<div class="domain-cell domain-cell-pv">访问量</div>
<div class="domain-cell domain-cell-time">最后访问时间</div>
<div class="domain-cell domain-cell-url">页面地址</div>
<div class="domain-cell domain-cell-title">{{ t('analytics.table.title') }}</div>
<div class="domain-cell domain-cell-pv">{{ t('analytics.table.pv') }}</div>
<div class="domain-cell domain-cell-time">{{ t('analytics.table.time') }}</div>
<div class="domain-cell domain-cell-url">{{ t('analytics.table.url') }}</div>
</div>
<div v-for="item in items" :key="item.postSlug" class="domain-table-row">
<div class="domain-cell domain-cell-title">
@@ -166,18 +166,18 @@
<div class="card">
<div class="card-title-row">
<h3 class="card-title">点赞页面排行榜</h3>
<h3 class="card-title">{{ t('analytics.likeRank') }}</h3>
</div>
<div v-if="loading" class="page-hint">加载中...</div>
<div v-if="loading" class="page-hint">{{ t('common.loading') }}</div>
<div v-else-if="error" class="page-error">{{ error }}</div>
<div v-else-if="likeStatsItems.length === 0" class="page-hint">暂无点赞数据</div>
<div v-else-if="likeStatsItems.length === 0" class="page-hint">{{ t('analytics.noLikeData') }}</div>
<div v-else class="domain-table-wrapper">
<div class="domain-table">
<div class="domain-table-header">
<div class="domain-cell domain-cell-rank">排名</div>
<div class="domain-cell domain-cell-title">页面标题</div>
<div class="domain-cell domain-cell-like">点赞数</div>
<div class="domain-cell domain-cell-url">页面地址</div>
<div class="domain-cell domain-cell-rank">{{ t('analytics.table.rank') }}</div>
<div class="domain-cell domain-cell-title">{{ t('analytics.table.title') }}</div>
<div class="domain-cell domain-cell-like">{{ t('analytics.table.like') }}</div>
<div class="domain-cell domain-cell-url">{{ t('analytics.table.url') }}</div>
</div>
<div
v-for="(item, index) in likeStatsItems"
@@ -214,6 +214,7 @@
<script setup lang="ts">
import { onMounted, onBeforeUnmount, ref, nextTick, watch, inject, computed } from "vue";
import type { Ref } from "vue";
import { useI18n } from "vue-i18n";
import * as echarts from "echarts";
import CountTo from "../../components/CountTo.vue";
import {
@@ -226,6 +227,8 @@ import {
} from "../../api/admin";
import { useSite } from "../../composables/useSite";
const { t } = useI18n();
const loading = ref(false);
const listLoading = ref(false);
const error = ref("");

View File

@@ -1,42 +1,42 @@
<template>
<div v-if="visible" class="modal-overlay">
<div class="modal">
<h3 class="modal-title">编辑评论</h3>
<h3 class="modal-title">{{ t('comments.editModal.title') }}</h3>
<div v-if="form" class="modal-body">
<div class="form-item">
<label class="form-label">访客昵称</label>
<label class="form-label">{{ t('comments.editModal.name') }}</label>
<input v-model="form.name" class="form-input" type="text" />
</div>
<div class="form-item">
<label class="form-label">访客邮箱</label>
<label class="form-label">{{ t('comments.editModal.email') }}</label>
<input v-model="form.email" class="form-input" type="email" />
</div>
<div class="form-item">
<label class="form-label">访客网址</label>
<label class="form-label">{{ t('comments.editModal.url') }}</label>
<input v-model="form.url" class="form-input" type="text" />
</div>
<div class="form-item">
<label class="form-label">页面标识</label>
<label class="form-label">{{ t('comments.editModal.postSlug') }}</label>
<input v-model="form.postSlug" class="form-input" type="text" />
</div>
<div class="form-item">
<label class="form-label">评论地址</label>
<label class="form-label">{{ t('comments.editModal.postUrl') }}</label>
<input v-model="form.postUrl" class="form-input" type="text" />
</div>
<div class="form-item">
<label class="form-label">评论内容</label>
<label class="form-label">{{ t('comments.editModal.content') }}</label>
<textarea v-model="form.contentText" class="form-input" rows="4"></textarea>
</div>
<div class="form-item">
<label class="form-label">评论状态</label>
<label class="form-label">{{ t('comments.editModal.status') }}</label>
<select v-model="form.status" class="form-input">
<option value="approved">已通过</option>
<option value="pending">待审核</option>
<option value="rejected">已拒绝</option>
<option value="approved">{{ t('comments.statusFilter.approved') }}</option>
<option value="pending">{{ t('comments.statusFilter.pending') }}</option>
<option value="rejected">{{ t('comments.statusFilter.rejected') }}</option>
</select>
</div>
<div class="form-item">
<label class="form-label">置顶权重1 为不置顶数值越大越靠前</label>
<label class="form-label">{{ t('comments.editModal.priority') }}</label>
<input
v-model.number="form.priority"
class="form-input"
@@ -47,7 +47,7 @@
</div>
<div class="modal-actions">
<button class="modal-btn secondary" type="button" @click="handleClose">
取消
{{ t('comments.editModal.cancel') }}
</button>
<button
class="modal-btn primary"
@@ -55,8 +55,8 @@
:disabled="saving"
@click="handleSubmit"
>
<span v-if="saving">保存中...</span>
<span v-else>保存</span>
<span v-if="saving">{{ t('comments.editModal.saving') }}</span>
<span v-else>{{ t('comments.editModal.save') }}</span>
</button>
</div>
</div>
@@ -64,6 +64,10 @@
</template>
<script setup lang="ts">
import { useI18n } from "vue-i18n";
const { t } = useI18n();
interface EditForm {
id: number;
name: string;

View File

@@ -1,29 +1,29 @@
<template>
<div class="page">
<h2 class="page-title">评论管理</h2>
<h2 class="page-title">{{ t('comments.title') }}</h2>
<div class="toolbar">
<div class="toolbar-left">
<select v-model="statusFilter" class="toolbar-select">
<option value="">全部状态</option>
<option value="approved">已通过</option>
<option value="pending">待审核</option>
<option value="rejected">已拒绝</option>
<option value="">{{ t('comments.statusFilter.all') }}</option>
<option value="approved">{{ t('comments.statusFilter.approved') }}</option>
<option value="pending">{{ t('comments.statusFilter.pending') }}</option>
<option value="rejected">{{ t('comments.statusFilter.rejected') }}</option>
</select>
</div>
<div class="toolbar-right">
<button class="toolbar-button" @click="goPage(1)">刷新</button>
<button class="toolbar-button" @click="goPage(1)">{{ t('comments.refresh') }}</button>
</div>
</div>
<div v-if="loading" class="page-hint">加载中...</div>
<div v-if="loading" class="page-hint">{{ t('common.loading') }}</div>
<div v-else-if="error" class="page-error">{{ error }}</div>
<div v-else>
<div class="comment-table">
<div class="table-header">
<div class="table-cell table-cell-author">用户</div>
<div class="table-cell table-cell-content">评论信息</div>
<div class="table-cell table-cell-path">评论地址</div>
<div class="table-cell table-cell-status">状态</div>
<div class="table-cell table-cell-actions">操作</div>
<div class="table-cell table-cell-author">{{ t('comments.table.author') }}</div>
<div class="table-cell table-cell-content">{{ t('comments.table.content') }}</div>
<div class="table-cell table-cell-path">{{ t('comments.table.path') }}</div>
<div class="table-cell table-cell-status">{{ t('comments.table.status') }}</div>
<div class="table-cell table-cell-actions">{{ t('comments.table.actions') }}</div>
</div>
<div v-for="item in filteredComments" :key="item.id" class="table-row">
<div class="table-cell table-cell-author">
@@ -40,7 +40,7 @@
<span v-if="item.isAdmin" class="cell-admin-tag">
<svg
viewBox="0 0 22 22"
aria-label="网站管理员"
:aria-label="t('comments.table.admin')"
role="img"
class="cwd-admin-icon"
style="
@@ -63,7 +63,7 @@
<span
class="cell-email-text"
@click="handleBlockEmail(item)"
title="屏蔽该邮箱"
:title="t('comments.actions.blockEmail')"
>
{{ item.email }}
</span>
@@ -73,7 +73,7 @@
<span
class="cell-ip-text"
@click="handleBlockIp(item)"
title="屏蔽该 IP"
:title="t('comments.actions.blockIp')"
>{{ item.ipAddress }}</span
>
</div>
@@ -104,7 +104,7 @@
:title="String(item.priority)"
class="cell-status cell-pin-flag"
>
置顶
{{ t('comments.actions.pin') }}
</span>
<span class="cell-status cell-likes-number" v-if="item.likes !== 0">
<PhThumbsUp :size="13" />
@@ -126,21 +126,21 @@
:value="item.status"
@change="handleStatusChange(item, $event)"
>
<option value="approved">通过</option>
<option value="pending">待审</option>
<option value="rejected">拒绝</option>
<option value="approved">{{ t('comments.actions.approve') }}</option>
<option value="pending">{{ t('comments.actions.pending') }}</option>
<option value="rejected">{{ t('comments.actions.reject') }}</option>
</select>
<button class="table-action" @click="openEdit(item)">编辑</button>
<button class="table-action" @click="openEdit(item)">{{ t('comments.actions.edit') }}</button>
<button
class="table-action table-action-danger"
@click="removeComment(item)"
>
删除
{{ t('comments.actions.delete') }}
</button>
</div>
</div>
</div>
<div v-if="filteredComments.length === 0" class="table-empty">暂无数据</div>
<div v-if="filteredComments.length === 0" class="table-empty">{{ t('comments.empty') }}</div>
</div>
<div v-if="pagination.total > 1" class="pagination">
<button
@@ -148,7 +148,7 @@
:disabled="pagination.page <= 1"
@click="goPage(pagination.page - 1)"
>
上一页
{{ t('comments.pagination.prev') }}
</button>
<button
class="pagination-button"
@@ -199,10 +199,10 @@
:disabled="pagination.page >= pagination.total"
@click="goPage(pagination.page + 1)"
>
下一页
{{ t('comments.pagination.next') }}
</button>
<div class="pagination-jump">
<span>跳转到</span>
<span>{{ t('comments.pagination.jumpTo') }}</span>
<input
v-model="jumpPageInput"
class="pagination-input"
@@ -211,8 +211,8 @@
:max="pagination.total"
@keyup.enter="handleJumpPage"
/>
<span></span>
<button class="pagination-button" @click="handleJumpPage">确定</button>
<span>{{ t('comments.pagination.page') }}</span>
<button class="pagination-button" @click="handleJumpPage">{{ t('comments.pagination.confirm') }}</button>
</div>
</div>
</div>
@@ -231,6 +231,10 @@ import "../../styles/markdown.css";
import { onMounted, ref, computed, watch, inject } from "vue";
import type { Ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useI18n } from "vue-i18n";
import { marked } from "marked";
import DOMPurify from "dompurify";
import ModalEdit from "./components/ModalEdit.vue";
import {
CommentItem,
CommentListResponse,
@@ -241,12 +245,13 @@ import {
blockIp,
blockEmail,
} from "../../api/admin";
import ModalEdit from "./components/ModalEdit.vue";
import { useSite } from "../../composables/useSite";
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
// ... existing refs ...
const comments = ref<CommentItem[]>([]);
const pagination = ref<{ page: number; total: number }>({ page: 1, total: 1 });
const loading = ref(false);
@@ -268,6 +273,11 @@ const editForm = ref<{
priority: number;
} | null>(null);
const searchKeyword = ref(""); // Missing ref in original code snippet but used in template?
// Wait, I saw searchKeyword in original code snippet. I must ensure I don't lose it.
// I will just use the replace string for the specific parts if possible, but I am replacing whole template.
// I need to be careful with script content.
const filteredComments = computed(() => {
if (!statusFilter.value) {
return comments.value;
@@ -275,6 +285,7 @@ const filteredComments = computed(() => {
return comments.value.filter((item) => item.status === statusFilter.value);
});
// ... visiblePages computed ...
const visiblePages = computed(() => {
const total = pagination.value.total;
const current = pagination.value.page;
@@ -308,17 +319,18 @@ function formatDate(value: number) {
function formatStatus(status: string) {
if (status === "approved") {
return "已通过";
return t('comments.statusFilter.approved');
}
if (status === "pending") {
return "待审核";
return t('comments.statusFilter.pending');
}
if (status === "rejected") {
return "已拒绝";
return t('comments.statusFilter.rejected');
}
return status;
}
// ... other functions ...
async function loadComments(page?: number) {
const targetPage = typeof page === "number" ? page : 1;
loading.value = true;
@@ -341,9 +353,7 @@ function updateRoutePage(page: number) {
} else {
query.p = String(page);
}
// Removed domain from query as it's now global state
delete query.domain;
router.push({ query });
}
@@ -388,7 +398,7 @@ function handleStatusChange(item: CommentItem, event: Event) {
}
async function removeComment(item: CommentItem) {
if (!window.confirm(`确认删除评论 ${item.id} 吗`)) {
if (!window.confirm(t('comments.confirmDelete', { id: item.id }))) {
return;
}
try {
@@ -403,12 +413,12 @@ async function handleBlockIp(item: CommentItem) {
if (!item.ipAddress) {
return;
}
if (!window.confirm(`确认将 IP ${item.ipAddress} 加入黑名单吗?`)) {
if (!window.confirm(t('comments.confirmBlockIp', { ip: item.ipAddress }))) {
return;
}
try {
const res = await blockIp(item.ipAddress);
window.alert(res.message || "已加入 IP 黑名单");
window.alert(res.message || t('comments.successBlockIp'));
} catch (e: any) {
error.value = e.message || "屏蔽 IP 失败";
}
@@ -418,12 +428,12 @@ async function handleBlockEmail(item: CommentItem) {
if (!item.email) {
return;
}
if (!window.confirm(`确认将邮箱 ${item.email} 加入黑名单吗?`)) {
if (!window.confirm(t('comments.confirmBlockEmail', { email: item.email }))) {
return;
}
try {
const res = await blockEmail(item.email);
window.alert(res.message || "已加入邮箱黑名单");
window.alert(res.message || t('comments.successBlockEmail'));
} catch (e: any) {
error.value = e.message || "屏蔽邮箱失败";
}
@@ -535,6 +545,7 @@ watch(currentSiteId, () => {
updateRoutePage(1);
loadComments(1);
});
</script>
<style scoped lang="less">

View File

@@ -1,6 +1,6 @@
<template>
<div class="page">
<h2 class="page-title">数据管理</h2>
<h2 class="page-title">{{ t('data.title') }}</h2>
<div
v-if="toastVisible"
class="toast"
@@ -11,57 +11,57 @@
<!-- 1. 评论数据 -->
<div class="card">
<h3 class="card-title">评论数据</h3>
<p class="card-desc">管理评论内容支持从其他评论框架迁移数据</p>
<h3 class="card-title">{{ t('data.sections.comments.title') }}</h3>
<p class="card-desc">{{ t('data.sections.comments.desc') }}</p>
<div class="action-row">
<span class="action-label">导出:</span>
<span class="action-label">{{ t('data.sections.comments.exportLabel') }}</span>
<button class="card-button secondary" :disabled="exporting" @click="handleExportComments">
<span v-if="exporting">导出中...</span>
<span v-else>导出 JSON</span>
<span v-if="exporting">{{ t('data.sections.comments.exporting') }}</span>
<span v-else>{{ t('data.sections.comments.exportJson') }}</span>
</button>
</div>
<div class="action-row">
<span class="action-label">导入:</span>
<span class="action-label">{{ t('data.sections.comments.importLabel') }}</span>
<select v-model="importSource" class="form-select" style="min-width:120px;">
<option value="cwd">CWD (.json)</option>
<option value="twikoo">Twikoo (.json)</option>
<option value="artalk">Artalk (.json)</option>
<option value="cwd">{{ t('data.sections.comments.source.cwd') }}</option>
<option value="twikoo">{{ t('data.sections.comments.source.twikoo') }}</option>
<option value="artalk">{{ t('data.sections.comments.source.artalk') }}</option>
</select>
<button class="card-button secondary" :disabled="importing" @click="triggerFileInput('comments')">
导入评论
{{ t('data.sections.comments.importButton') }}
</button>
</div>
</div>
<!-- 2. 系统配置 -->
<div class="card">
<h3 class="card-title">系统配置</h3>
<p class="card-desc">管理后台设置邮件配置黑名单等</p>
<h3 class="card-title">{{ t('data.sections.config.title') }}</h3>
<p class="card-desc">{{ t('data.sections.config.desc') }}</p>
<div class="action-row">
<button class="card-button secondary" :disabled="exporting" @click="handleExportConfig">导出配置</button>
<button class="card-button secondary" :disabled="importing" @click="triggerFileInput('config')">导入配置</button>
<button class="card-button secondary" :disabled="exporting" @click="handleExportConfig">{{ t('data.sections.config.export') }}</button>
<button class="card-button secondary" :disabled="importing" @click="triggerFileInput('config')">{{ t('data.sections.config.import') }}</button>
</div>
</div>
<!-- 3. 访问统计 -->
<div class="card">
<h3 class="card-title">访问统计</h3>
<p class="card-desc">管理文章访问量点赞数及每日访问趋势</p>
<h3 class="card-title">{{ t('data.sections.stats.title') }}</h3>
<p class="card-desc">{{ t('data.sections.stats.desc') }}</p>
<div class="action-row">
<button class="card-button secondary" :disabled="exporting" @click="handleExportStats">导出统计</button>
<button class="card-button secondary" :disabled="importing" @click="triggerFileInput('stats')">导入统计</button>
<button class="card-button secondary" :disabled="exporting" @click="handleExportStats">{{ t('data.sections.stats.export') }}</button>
<button class="card-button secondary" :disabled="importing" @click="triggerFileInput('stats')">{{ t('data.sections.stats.import') }}</button>
</div>
</div>
<!-- 4. 全量备份 -->
<div class="card">
<h3 class="card-title">全量备份</h3>
<p class="card-desc">一键备份或恢复系统所有数据评论 + 配置 + 统计</p>
<h3 class="card-title">{{ t('data.sections.backup.title') }}</h3>
<p class="card-desc">{{ t('data.sections.backup.desc') }}</p>
<div class="action-row">
<button class="card-button secondary" :disabled="exporting" @click="handleExportBackup">全量导出</button>
<button class="card-button secondary" :disabled="importing" @click="triggerFileInput('backup')">全量恢复</button>
<button class="card-button secondary" :disabled="exporting" @click="handleExportBackup">{{ t('data.sections.backup.export') }}</button>
<button class="card-button secondary" :disabled="importing" @click="triggerFileInput('backup')">{{ t('data.sections.backup.import') }}</button>
</div>
</div>
@@ -76,7 +76,7 @@
<!-- 导入日志 -->
<div v-if="importLogs.length > 0" class="log-container">
<div class="log-title">操作日志</div>
<div class="log-title">{{ t('data.logs.title') }}</div>
<div class="log-list">
<div v-for="(log, index) in importLogs" :key="index" class="log-item">
{{ log }}
@@ -87,26 +87,27 @@
<!-- 前缀确认弹窗 -->
<div v-if="showPrefixModal" class="modal-overlay">
<div class="modal">
<h3 class="modal-title">检测到 URL 缺失前缀</h3>
<h3 class="modal-title">{{ t('data.prefixModal.title') }}</h3>
<p class="modal-desc">
检测到 <strong>{{ missingPrefixCount }}</strong> 条评论的 URL
不存在域名前缀http/https<br />
是否在导入时统一添加
{{ t('data.prefixModal.descPart1') }}
<strong>{{ missingPrefixCount }}</strong>
{{ t('data.prefixModal.descPart2') }}<br />
{{ t('data.prefixModal.descPart3') }}
</p>
<div class="form-group">
<label class="form-label">域名前缀 (例如 https://example.me)</label>
<label class="form-label">{{ t('data.prefixModal.label') }}</label>
<input
v-model="urlPrefix"
class="form-input"
placeholder="请输入域名前缀"
:placeholder="t('data.prefixModal.placeholder')"
@keyup.enter="confirmPrefix"
/>
</div>
<div class="modal-actions">
<div class="modal-actions">
<button class="modal-btn secondary" @click="cancelPrefix">
直接导入 (不添加)
{{ t('data.prefixModal.skip') }}
</button>
<button class="modal-btn primary" @click="confirmPrefix">添加并导入</button>
<button class="modal-btn primary" @click="confirmPrefix">{{ t('data.prefixModal.confirm') }}</button>
</div>
</div>
</div>
@@ -115,6 +116,7 @@
<script setup lang="ts">
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import {
exportComments, importComments,
exportConfig, importConfig,
@@ -123,6 +125,8 @@ import {
} from "../../api/admin";
import { useSite } from "../../composables/useSite";
const { t } = useI18n();
const exporting = ref(false);
const importing = ref(false);
const importSource = ref("cwd");
@@ -171,9 +175,9 @@ async function executeExport(apiFunc: () => Promise<any>, fileNamePrefix: string
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
showToast("导出成功", "success");
showToast(t('data.messages.exportSuccess'), "success");
} catch (e: any) {
showToast(e.message || "导出失败", "error");
showToast(e.message || t('data.messages.exportFailed'), "error");
} finally {
exporting.value = false;
}
@@ -188,7 +192,7 @@ const handleExportBackup = () => executeExport(exportBackup, 'cwd-full-backup');
// 触发文件选择
function triggerFileInput(mode: string) {
currentImportMode.value = mode;
importLogs.value = []; // 清空日志
importLogs.value = [];
if (fileInput.value) {
fileInput.value.value = ''; // 重置 input
fileInput.value.click();
@@ -202,7 +206,7 @@ async function handleFileChange(event: Event) {
if (!file) return;
importing.value = true;
addLog(`开始导入: ${file.name} (模式: ${currentImportMode.value})`);
addLog(t('data.messages.importStart', { name: file.name, mode: currentImportMode.value }));
const reader = new FileReader();
reader.onload = async (e) => {
@@ -212,10 +216,10 @@ async function handleFileChange(event: Event) {
try {
json = JSON.parse(content);
} catch (parseError) {
throw new Error("JSON 解析失败,请检查文件格式");
throw new Error(t('data.messages.jsonParseFailed'));
}
addLog("文件解析成功,开始处理...");
addLog(t('data.messages.fileParseSuccess'));
switch (currentImportMode.value) {
case 'comments':
@@ -234,15 +238,15 @@ async function handleFileChange(event: Event) {
} catch (err: any) {
console.error(err);
addLog(`错误: ${err.message}`);
addLog(t('data.messages.errorWithMessage', { msg: err.message }));
showToast(err.message, "error");
importing.value = false;
}
};
reader.onerror = () => {
addLog("读取文件失败");
showToast("读取文件失败", "error");
addLog(t('data.messages.readFileFailedLog'));
showToast(t('data.messages.fileReadFailed'), "error");
importing.value = false;
};
@@ -253,27 +257,27 @@ async function handleFileChange(event: Event) {
async function processImportConfig(data: any) {
const res = await importConfig(data);
addLog(res.message);
showToast("配置导入成功");
showToast(t('data.messages.importConfigSuccess'));
importing.value = false;
}
async function processImportStats(data: any) {
const res = await importStats(data);
addLog(res.message);
showToast("统计数据导入成功");
showToast(t('data.messages.importStatsSuccess'));
importing.value = false;
}
async function processImportBackup(data: any) {
const res = await importBackup(data);
addLog(res.message);
showToast("全量恢复成功");
showToast(t('data.messages.importBackupSuccess'));
importing.value = false;
}
async function processImportComments(json: any) {
const comments = Array.isArray(json) ? json : [json];
addLog(`解析到 ${comments.length} 条评论数据`);
addLog(t('data.messages.parsedCommentsCount', { count: comments.length }));
// 检查 URL 前缀 (仅针对评论导入)
let missingCount = 0;
@@ -287,7 +291,7 @@ async function processImportComments(json: any) {
}
if (missingCount > 0) {
addLog(`检测到 ${missingCount} 条 URL 缺失前缀,等待用户确认...`);
addLog(t('data.messages.detectMissingPrefix', { count: missingCount }));
missingPrefixCount.value = missingCount;
pendingJson.value = comments;
showPrefixModal.value = true;
@@ -300,8 +304,8 @@ async function processImportComments(json: any) {
async function executeImportComments(comments: any[]) {
try {
const res = await importComments(comments);
addLog(`导入完成: ${res.message}`);
showToast("评论导入成功");
addLog(t('data.messages.importCommentsDone', { message: res.message }));
showToast(t('data.messages.importCommentsSuccess'));
} catch (err: any) {
throw err;
} finally {
@@ -313,7 +317,7 @@ async function executeImportComments(comments: any[]) {
// 前缀确认逻辑
async function confirmPrefix() {
if (!urlPrefix.value) {
showToast("请输入域名前缀", "error");
showToast(t('data.messages.prefixRequired'), "error");
return;
}
@@ -343,13 +347,13 @@ async function confirmPrefix() {
});
showPrefixModal.value = false;
addLog(`已添加前缀,继续导入...`);
addLog(t('data.messages.prefixAdded'));
await executeImportComments(comments);
}
function cancelPrefix() {
showPrefixModal.value = false;
addLog("用户跳过前缀添加");
addLog(t('data.messages.skipPrefix'));
executeImportComments(pendingJson.value);
}

View File

@@ -4,7 +4,7 @@
<button
class="layout-menu-toggle"
@click="toggleSider"
aria-label="切换菜单"
:aria-label="t('layout.toggleMenu')"
type="button"
>
<PhTextIndent :size="20" />
@@ -25,7 +25,7 @@
</div>
<div class="layout-actions">
<a class="layout-button" href="https://cwd.js.org" target="_blank">
使用文档
{{ t('layout.docs') }}
</a>
<a class="layout-button" href="https://github.com/anghunk/cwd" target="_blank">
Github
@@ -40,20 +40,20 @@
<PhMoon v-else-if="theme === 'dark'" :size="16" />
<PhAirplay v-else :size="16" />
</button>
<button class="layout-button" @click="handleLogout">退出</button>
<button class="layout-button" @click="handleLogout">{{ t('layout.logout') }}</button>
</div>
<button
class="layout-actions-toggle"
@click="toggleActions"
aria-label="更多操作"
:aria-label="t('layout.moreActions')"
type="button"
>
<PhDotsThreeVertical :size="20" bold />
</button>
<div v-if="isActionsOpen" class="layout-actions-dropdown">
<button class="layout-actions-item" type="button" @click="openDocs">
使用文档
{{ t('layout.docs') }}
</button>
<button class="layout-actions-item" type="button" @click="openGithub">
Github
@@ -63,7 +63,7 @@
type="button"
@click="handleLogoutFromActions"
>
退出
{{ t('layout.logout') }}
</button>
</div>
</div>
@@ -92,7 +92,7 @@
@click="goComments"
>
<PhChatCircleDots class="menu-item-icon" :size="18" />
<span>评论管理</span>
<span>{{ t('menu.comments') }}</span>
</li>
<li
class="menu-item"
@@ -100,7 +100,7 @@
@click="goStats"
>
<PhSquaresFour class="menu-item-icon" :size="18" />
<span>数据看板</span>
<span>{{ t('menu.stats') }}</span>
</li>
<li
class="menu-item"
@@ -108,7 +108,7 @@
@click="goAnalytics"
>
<PhChartBar class="menu-item-icon" :size="18" />
<span>访问统计</span>
<span>{{ t('menu.analytics') }}</span>
</li>
<li
class="menu-item"
@@ -116,7 +116,7 @@
@click="goSettings"
>
<PhGear class="menu-item-icon" :size="18" />
<span>网站设置</span>
<span>{{ t('menu.settings') }}</span>
</li>
<li
class="menu-item"
@@ -124,7 +124,7 @@
@click="goData"
>
<PhDatabase class="menu-item-icon" :size="18" />
<span>数据管理</span>
<span>{{ t('menu.data') }}</span>
</li>
</ul>
<div class="layout-sider-footer" @click="openVersionModal">
@@ -141,36 +141,35 @@
</div>
<div v-if="versionModalVisible" class="modal-overlay" @click.self="closeVersionModal">
<div class="modal">
<h3 class="modal-title">版本信息</h3>
<h3 class="modal-title">{{ t('layout.version.title') }}</h3>
<div class="modal-body">
<p class="modal-row">
<span class="modal-label">API 地址</span>
<span class="modal-value">{{ checkedApiBaseUrl || "未配置" }}</span>
<span class="modal-label">{{ t('layout.version.apiAddress') }}</span>
<span class="modal-value">{{ checkedApiBaseUrl || t('layout.version.notConfigured') }}</span>
</p>
<p class="modal-row">
<span class="modal-label">接口版本</span>
<span class="modal-label">{{ t('layout.version.apiVersion') }}</span>
<span class="modal-value">
{{ apiVersion || (apiVersionError ? "未获取到" : "加载中...") }}
{{ apiVersion || (apiVersionError ? t('layout.version.notFetched') : t('layout.version.loading')) }}
</span>
</p>
<p class="modal-row">
<span class="modal-label">后台版本</span>
<span class="modal-label">{{ t('layout.version.adminVersion') }}</span>
<span class="modal-value">{{ adminVersion }}</span>
</p>
<p v-if="apiVersion && apiVersion === adminVersion" class="modal-status">
当前后台与接口版本一致可以正常使用
{{ t('layout.version.match') }}
</p>
<p v-else-if="apiVersion && apiVersion !== adminVersion" class="modal-status">
当前后台与接口版本不一致推荐将 API 服务更新到与后台版本一致
以避免潜在的兼容性问题
{{ t('layout.version.mismatch') }}
</p>
<p v-else-if="apiVersionError" class="modal-status">
无法获取接口版本{{ apiVersionError }}
{{ t('layout.version.fetchError') }} {{ apiVersionError }}
</p>
</div>
<div class="modal-actions">
<button class="modal-btn" type="button" @click="closeVersionModal">
我知道了
{{ t('layout.version.ok') }}
</button>
</div>
</div>
@@ -181,6 +180,7 @@
<script setup lang="ts">
import { ref, onMounted, provide, computed } from "vue";
import { useRouter, useRoute } from "vue-router";
import { useI18n } from "vue-i18n";
import { logoutAdmin, fetchAdminDisplaySettings, fetchSiteList } from "../../api/admin";
import { useTheme } from "../../composables/useTheme";
import { useSite } from "../../composables/useSite";
@@ -191,6 +191,7 @@ const SITE_TITLE_KEY = "cwd_admin_site_title";
const router = useRouter();
const route = useRoute();
const { t } = useI18n();
const { theme, setTheme } = useTheme();
const { currentSiteId } = useSite();
@@ -204,9 +205,9 @@ const versionModalVisible = ref(false);
const layoutTitle = ref(localStorage.getItem(SITE_TITLE_KEY) || "CWD 评论系统");
const themeTitle = computed(() => {
if (theme.value === "light") return "明亮模式";
if (theme.value === "dark") return "暗黑模式";
return "跟随系统";
if (theme.value === "light") return t('layout.theme.light');
if (theme.value === "dark") return t('layout.theme.dark');
return t('layout.theme.system');
});
function cycleTheme() {
@@ -221,7 +222,7 @@ const defaultSiteId = "default";
function getSiteLabel(value: string) {
if (!value || value === "default") {
return "默认站点";
return t('layout.defaultSite');
}
return value;
}

View File

@@ -1,6 +1,6 @@
<template>
<div class="page">
<h2 class="page-title">网站设置</h2>
<h2 class="page-title">{{ t('settings.title') }}</h2>
<div
v-if="toastVisible"
class="toast"
@@ -8,7 +8,7 @@
>
{{ toastMessage }}
</div>
<div v-if="loading" class="page-hint">加载中...</div>
<div v-if="loading" class="page-hint">{{ t('common.loading') }}</div>
<div v-else>
<div class="settings-tabs">
<button
@@ -17,7 +17,7 @@
:class="{ 'settings-tab-active': activeTab === 'comment' }"
@click="activeTab = 'comment'"
>
评论与安全
{{ t('settings.tabs.comment') }}
</button>
<button
type="button"
@@ -25,15 +25,7 @@
:class="{ 'settings-tab-active': activeTab === 'feature' }"
@click="activeTab = 'feature'"
>
功能开关
</button>
<button
type="button"
class="settings-tab"
:class="{ 'settings-tab-active': activeTab === 'display' }"
@click="activeTab = 'display'"
>
后台显示
{{ t('settings.tabs.feature') }}
</button>
<button
type="button"
@@ -41,7 +33,7 @@
:class="{ 'settings-tab-active': activeTab === 'emailNotify' }"
@click="activeTab = 'emailNotify'"
>
邮箱提醒
{{ t('settings.tabs.emailNotify') }}
</button>
<button
type="button"
@@ -49,7 +41,15 @@
:class="{ 'settings-tab-active': activeTab === 'telegramNotify' }"
@click="activeTab = 'telegramNotify'"
>
Telegram 通知
{{ t('settings.tabs.telegramNotify') }}
</button>
<button
type="button"
class="settings-tab"
:class="{ 'settings-tab-active': activeTab === 'display' }"
@click="activeTab = 'display'"
>
{{ t('settings.tabs.display') }}
</button>
</div>
<transition name="tab-fade" mode="out-in">
@@ -57,19 +57,19 @@
<template v-if="activeTab === 'comment'">
<div class="card">
<div class="card-header">
<div class="card-title">评论显示配置</div>
<div class="card-title">{{ t('settings.comment.title') }}</div>
</div>
<div class="card-body">
<div class="form-item">
<label class="form-label">管理员邮箱</label>
<label class="form-label">{{ t('settings.comment.adminEmail') }}</label>
<input v-model="commentAdminEmail" class="form-input" type="email" />
</div>
<div class="form-item">
<label class="form-label">博主标签文字留空使用默认图标</label>
<label class="form-label">{{ t('settings.comment.adminBadge') }}</label>
<input v-model="commentAdminBadge" class="form-input" type="text" />
</div>
<div class="form-item">
<label class="form-label">是否开启博主标签显示</label>
<label class="form-label">{{ t('settings.comment.adminEnabled') }}</label>
<label class="switch">
<input v-model="commentAdminEnabled" type="checkbox" />
<span class="slider" />
@@ -77,13 +77,13 @@
</div>
<div class="form-item">
<label class="form-label"
>头像前缀默认https://gravatar.com/avatar</label
>{{ t('settings.comment.avatarPrefix') }}</label
>
<input v-model="avatarPrefix" class="form-input" type="text" />
</div>
<h3 class="card-title">安全设置</h3>
<h3 class="card-title">{{ t('settings.comment.securityTitle') }}</h3>
<div class="form-item">
<label class="form-label">新评论是否审核后再显示</label>
<label class="form-label">{{ t('settings.comment.requireReview') }}</label>
<label class="switch">
<input v-model="requireReview" type="checkbox" />
<span class="slider" />
@@ -91,31 +91,31 @@
</div>
<div class="form-item">
<label class="form-label">
管理员评论密钥设置后前台使用管理员邮箱评论需输入此密钥
{{ t('settings.comment.adminKey') }}
</label>
<input
v-model="commentAdminKey"
class="form-input"
placeholder="输入密钥以设置或修改"
:placeholder="t('settings.comment.adminKeyPlaceholder')"
autocomplete="new-password"
/>
</div>
<div class="form-item">
<label class="form-label">
允许调用的域名设置后仅匹配域名可调用前台评论组件留空则不限制使用空格或者逗号进行分割
{{ t('settings.comment.allowedDomains') }}
</label>
<TagInput v-model="allowedDomainTags" />
</div>
<div class="form-item">
<label class="form-label">
IP 黑名单多个 IP 用逗号或换行分隔留空则不限制
{{ t('settings.comment.blockedIps') }}
</label>
<TagInput v-model="blockedIpTags" />
</div>
<div class="form-item">
<label class="form-label"
>邮箱黑名单多个邮箱用逗号或换行分隔留空则不限制</label
>{{ t('settings.comment.blockedEmails') }}</label
>
<TagInput v-model="blockedEmailTags" />
</div>
@@ -126,8 +126,8 @@
:disabled="savingComment"
@click="saveComment"
>
<span v-if="savingComment">保存中...</span>
<span v-else>保存</span>
<span v-if="savingComment">{{ t('settings.comment.saving') }}</span>
<span v-else>{{ t('settings.comment.save') }}</span>
</button>
</div>
</div>
@@ -136,66 +136,80 @@
<template v-else-if="activeTab === 'feature'">
<div class="card feature">
<div class="card-header">
<div class="card-title">功能开关</div>
<div class="card-title">{{ t('settings.feature.title') }}</div>
</div>
<div class="card-body">
<div class="form-item bg">
<div class="form-item-flex">
<label class="form-label">开启文章点赞功能</label>
<label class="form-label">{{ t('settings.feature.articleLike') }}</label>
<label class="switch">
<input v-model="enableArticleLike" type="checkbox" />
<span class="slider" />
</label>
</div>
<div class="form-hint">
开启后评论区顶部会显示的文章点赞喜欢按钮
{{ t('settings.feature.articleLikeHint') }}
</div>
</div>
<div class="form-item bg">
<div class="form-item-flex">
<label class="form-label">开启评论点赞功能</label>
<label class="form-label">{{ t('settings.feature.commentLike') }}</label>
<label class="switch">
<input v-model="enableCommentLike" type="checkbox" />
<span class="slider" />
</label>
</div>
<div class="form-hint">
开启后评论列表中的每条评论都会显示点赞按钮
{{ t('settings.feature.commentLikeHint') }}
</div>
</div>
<div class="form-item bg">
<div class="form-item-flex">
<label class="form-label">评论列表图片灯箱模式</label>
<label class="form-label">{{ t('settings.feature.imageLightbox') }}</label>
<label class="switch">
<input v-model="enableImageLightbox" type="checkbox" />
<span class="slider" />
</label>
</div>
<div class="form-hint">开启后点击评论中的图片时会全屏放大预览</div>
<div class="form-hint">{{ t('settings.feature.imageLightboxHint') }}</div>
</div>
<div class="form-item">
<label class="form-label">评论框提示文案 (Placeholder)</label>
<label class="form-label">{{ t('settings.feature.placeholder') }}</label>
<textarea
v-model="commentPlaceholder"
class="form-input"
rows="3"
style="height: 90px; resize: none"
placeholder="默认:写下你的评论,可换行书写提示"
:placeholder="t('settings.feature.placeholderHint')"
></textarea>
<div class="form-hint">
自定义评论输入框的提示文字支持换行留空则使用默认值
{{ t('settings.feature.placeholderHint') }}
</div>
</div>
<div class="form-item">
<label class="form-label">{{ t('settings.feature.widgetLanguage') }}</label>
<select v-model="widgetLanguage" class="form-input">
<option value="auto">Auto (Browser Default)</option>
<option v-for="lang in languageOptions" :key="lang.value" :value="lang.value">
{{ lang.label }} ({{ lang.value }})
</option>
</select>
<div class="form-hint">
{{ t('settings.feature.widgetLanguageHint') }}
</div>
</div>
<div class="card-actions">
<button
class="card-button"
:disabled="savingFeature"
@click="saveFeature"
>
<span v-if="savingFeature">保存中...</span>
<span v-else>保存</span>
<span v-if="savingFeature">{{ t('settings.feature.saving') }}</span>
<span v-else>{{ t('settings.feature.save') }}</span>
</button>
</div>
</div>
@@ -204,27 +218,38 @@
<template v-else-if="activeTab === 'display'">
<div class="card">
<div class="card-header">
<div class="card-title">后台显示设置</div>
<div class="card-title">{{ t('settings.display.title') }}</div>
</div>
<div class="card-body">
<div class="form-item">
<label class="form-label">后台标题</label>
<label class="form-label">{{ t('settings.display.layoutTitle') }}</label>
<input
v-model="adminLayoutTitle"
class="form-input"
type="text"
placeholder="CWD 评论系统"
/>
<div class="form-hint">显示在后台顶部导航栏左侧</div>
<div class="form-hint">{{ t('settings.display.layoutTitleHint') }}</div>
</div>
<div class="form-item">
<label class="form-label">{{ t('settings.display.adminLanguage') }}</label>
<select v-model="adminLanguage" class="form-input">
<option v-for="lang in languageOptions" :key="lang.value" :value="lang.value">
{{ lang.label }} ({{ lang.value }})
</option>
</select>
<div class="form-hint">{{ t('settings.display.adminLanguageHint') }}</div>
</div>
<div class="card-actions">
<button
class="card-button"
:disabled="savingDisplay"
@click="saveDisplay"
>
<span v-if="savingDisplay">保存中...</span>
<span v-else>保存</span>
<span v-if="savingDisplay">{{ t('settings.display.saving') }}</span>
<span v-else>{{ t('settings.display.save') }}</span>
</button>
</div>
</div>
@@ -233,11 +258,11 @@
<template v-else-if="activeTab === 'emailNotify'">
<div class="card">
<div class="card-header">
<div class="card-title">通知邮箱设置</div>
<div class="card-title">{{ t('settings.emailNotify.title') }}</div>
</div>
<div class="card-body">
<div class="form-item">
<label class="form-label">开启邮件通知</label>
<label class="form-label">{{ t('settings.emailNotify.enable') }}</label>
<label class="switch">
<input v-model="emailGlobalEnabled" type="checkbox" />
<span class="slider" />
@@ -245,10 +270,10 @@
</div>
<div class="divider"></div>
<h4 class="card-subtitle">1. SMTP 发件配置</h4>
<h4 class="card-subtitle">{{ t('settings.emailNotify.smtpTitle') }}</h4>
<div class="form-item">
<label class="form-label">邮件服务商</label>
<label class="form-label">{{ t('settings.emailNotify.provider') }}</label>
<select
v-model="smtpProvider"
class="form-input"
@@ -262,7 +287,7 @@
<div v-if="smtpProvider === 'custom'">
<div class="form-item">
<label class="form-label">SMTP 服务器</label>
<label class="form-label">{{ t('settings.emailNotify.host') }}</label>
<input
v-model="smtpHost"
class="form-input"
@@ -270,7 +295,7 @@
/>
</div>
<div class="form-item">
<label class="form-label">SMTP 端口</label>
<label class="form-label">{{ t('settings.emailNotify.port') }}</label>
<input
v-model="smtpPort"
class="form-input"
@@ -279,7 +304,7 @@
/>
</div>
<div class="form-item">
<label class="form-label">SSL 安全连接</label>
<label class="form-label">{{ t('settings.emailNotify.secure') }}</label>
<label class="switch">
<input v-model="smtpSecure" type="checkbox" />
<span class="slider" />
@@ -288,7 +313,7 @@
</div>
<div class="form-item">
<label class="form-label">发件邮箱账号</label>
<label class="form-label">{{ t('settings.emailNotify.user') }}</label>
<input
v-model="smtpUser"
class="form-input"
@@ -296,37 +321,24 @@
/>
</div>
<div class="form-item">
<label class="form-label">授权码/密码</label>
<label class="form-label">{{ t('settings.emailNotify.pass') }}</label>
<input
v-model="smtpPass"
class="form-input"
type="text"
placeholder="QQ邮箱请使用授权码"
/>
<div v-if="smtpProvider === 'qq'" class="form-hint">
注意QQ 邮箱必须使用<a
href="https://service.mail.qq.com/detail/0/75"
target="_blank"
>授权码</a
>而非 QQ 密码<br />
请登录 QQ 邮箱网页版设置 - 账户中开启 POP3/SMTP
服务并生成授权码
</div>
<div v-else-if="smtpProvider === '163'" class="form-hint">
注意163 邮箱必须使用授权码而非登录密码<br />
请登录 163 邮箱网页版设置 -
POP3/SMTP/IMAP中开启服务并生成授权码
</div>
<div v-if="smtpProvider === 'qq'" class="form-hint" v-html="t('settings.emailNotify.qqHint')"></div>
<div v-else-if="smtpProvider === '163'" class="form-hint" v-html="t('settings.emailNotify.163Hint')"></div>
</div>
<div class="divider"></div>
<h4 class="card-subtitle">2. 邮件模板设置</h4>
<h4 class="card-subtitle">{{ t('settings.emailNotify.templateTitle') }}</h4>
<div class="form-item">
<label class="form-label">管理员通知模板 (HTML)</label>
<label class="form-label">{{ t('settings.emailNotify.adminTemplate') }}</label>
<div class="form-hint">
可用变量${commentAuthor} (评论人昵称), ${postTitle} (文章标题),
${postUrl} (文章链接), ${commentContent} (评论内容)
{{ t('settings.emailNotify.adminTemplateHint') }}
</div>
<textarea
v-model="templateAdmin"
@@ -337,11 +349,9 @@
</div>
<div class="form-item">
<label class="form-label">回复通知模板 (HTML)</label>
<label class="form-label">{{ t('settings.emailNotify.replyTemplate') }}</label>
<div class="form-hint">
可用变量${toName} (接收人昵称), ${replyAuthor} (回复人昵称),
${postTitle} (文章标题), ${postUrl} (文章链接), ${parentComment}
(原评论), ${replyContent} (回复内容)
{{ t('settings.emailNotify.replyTemplateHint') }}
</div>
<textarea
v-model="templateReply"
@@ -363,19 +373,19 @@
:disabled="testingEmail"
@click="testEmail"
>
<span v-if="testingEmail">发送中...</span>
<span v-else>发送测试邮件</span>
<span v-if="testingEmail">{{ t('settings.emailNotify.testingBtn') }}</span>
<span v-else>{{ t('settings.emailNotify.testBtn') }}</span>
</button>
<button
class="card-button secondary"
style="margin-left: auto"
@click="resetTemplatesToDefault"
>
恢复默认模板
{{ t('settings.emailNotify.resetBtn') }}
</button>
<button class="card-button" :disabled="savingEmail" @click="saveEmail">
<span v-if="savingEmail">保存中...</span>
<span v-else>保存配置</span>
<span v-if="savingEmail">{{ t('settings.emailNotify.saving') }}</span>
<span v-else>{{ t('settings.emailNotify.save') }}</span>
</button>
</div>
</div>
@@ -384,44 +394,36 @@
<template v-else-if="activeTab === 'telegramNotify'">
<div class="card">
<div class="card-header">
<div class="card-title">Telegram 通知设置</div>
<div class="card-title">{{ t('settings.telegramNotify.title') }}</div>
</div>
<div class="card-body">
<div class="form-item">
<label class="form-label">开启 Telegram 通知</label>
<label class="form-label">{{ t('settings.telegramNotify.enable') }}</label>
<label class="switch">
<input v-model="telegramNotifyEnabled" type="checkbox" />
<span class="slider" />
</label>
</div>
<div class="form-item">
<label class="form-label">Bot Token</label>
<label class="form-label">{{ t('settings.telegramNotify.botToken') }}</label>
<input
v-model="telegramBotToken"
class="form-input"
type="text"
type="password"
placeholder="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
autocomplete="new-password"
/>
<div class="form-hint">
Telegram 中搜索
<a href="https://t.me/BotFather" target="_blank">@BotFather</a>
创建机器人获取 Token
</div>
<div class="form-hint" v-html="t('settings.telegramNotify.botTokenHint')"></div>
</div>
<div class="form-item">
<label class="form-label">Chat ID</label>
<label class="form-label">{{ t('settings.telegramNotify.chatId') }}</label>
<input
v-model="telegramChatId"
class="form-input"
type="text"
placeholder="123456789"
/>
<div class="form-hint">
这是接收通知的用户 ID 或群组 ID可以先给机器人发消息然后通过 API
获取 ID或者使用
<a href="https://t.me/userinfobot" target="_blank">@userinfobot</a>
查询
</div>
<div class="form-hint" v-html="t('settings.telegramNotify.chatIdHint')"></div>
</div>
<div class="card-actions" style="justify-content: space-between">
@@ -430,8 +432,8 @@
:disabled="settingUpWebhook"
@click="doSetupWebhook"
>
<span v-if="settingUpWebhook">设置中...</span>
<span v-else>一键设置 Webhook</span>
<span v-if="settingUpWebhook">{{ t('settings.telegramNotify.webhookSetting') }}</span>
<span v-else>{{ t('settings.telegramNotify.webhookBtn') }}</span>
</button>
<button
class="card-button secondary"
@@ -439,16 +441,16 @@
@click="testTelegram"
style="margin-right: auto"
>
<span v-if="testingTelegram">发送中...</span>
<span v-else>发送测试消息</span>
<span v-if="testingTelegram">{{ t('settings.telegramNotify.testingBtn') }}</span>
<span v-else>{{ t('settings.telegramNotify.testBtn') }}</span>
</button>
<button
class="card-button"
:disabled="savingTelegram"
@click="saveTelegram"
>
<span v-if="savingTelegram">保存中...</span>
<span v-else>保存</span>
<span v-if="savingTelegram">{{ t('settings.telegramNotify.saving') }}</span>
<span v-else>{{ t('settings.telegramNotify.save') }}</span>
</button>
</div>
</div>
@@ -463,6 +465,7 @@
<script setup lang="ts">
import { onMounted, ref, type Ref, inject, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useI18n } from "vue-i18n";
import {
fetchCommentSettings,
saveCommentSettings,
@@ -578,6 +581,28 @@ const savingTelegram = ref(false);
const settingUpWebhook = ref(false);
const testingTelegram = ref(false);
const languageOptions = [
{ value: 'en-US', label: 'English' },
{ value: 'zh-CN', label: '简体中文' },
{ value: 'zh-TW', label: '繁體中文' },
{ value: 'es', label: 'Español' },
{ value: 'pt', label: 'Português' },
{ value: 'fr', label: 'Français' },
{ value: 'de', label: 'Deutsch' },
{ value: 'ja', label: '日本語' },
{ value: 'ko', label: '한국어' },
{ value: 'ru', label: 'Русский' },
{ value: 'it', label: 'Italiano' },
{ value: 'nl', label: 'Nederlands' },
{ value: 'ar', label: 'العربية' },
{ value: 'hi', label: 'हिन्दी' },
{ value: 'id', label: 'Bahasa Indonesia' },
];
const { t, locale } = useI18n();
const adminLanguage = ref("zh-CN");
const widgetLanguage = ref("auto");
const route = useRoute();
const router = useRouter();
@@ -731,6 +756,14 @@ async function load() {
enableCommentLike.value = featureRes.enableCommentLike;
enableImageLightbox.value = featureRes.enableImageLightbox;
commentPlaceholder.value = featureRes.commentPlaceholder || "";
adminLanguage.value = featureRes.adminLanguage || "zh-CN";
widgetLanguage.value = featureRes.widgetLanguage || "auto";
// Sync locale
if (featureRes.adminLanguage) {
locale.value = featureRes.adminLanguage;
localStorage.setItem('admin_language', featureRes.adminLanguage);
}
telegramBotToken.value = telegramRes.botToken || "";
telegramChatId.value = telegramRes.chatId || "";
@@ -861,10 +894,9 @@ async function saveComment() {
}),
]);
showToast(commentRes.message || "保存成功", "success");
showToast(commentRes.message || t('common.saveSuccess'), "success");
} catch (e: any) {
message.value = e.message || "保存失败";
messageType.value = "error";
showToast(e.message || t('common.saveFailed'), "error");
} finally {
savingComment.value = false;
}
@@ -880,13 +912,13 @@ async function saveFeature() {
enableCommentLike: enableCommentLike.value,
enableImageLightbox: enableImageLightbox.value,
commentPlaceholder: commentPlaceholder.value,
widgetLanguage: widgetLanguage.value,
}),
]);
showToast(featureRes.message || "保存成功", "success");
showToast(featureRes.message || t('common.saveSuccess'), "success");
} catch (e: any) {
message.value = e.message || "保存失败";
messageType.value = "error";
showToast(e.message || t('common.saveFailed'), "error");
} finally {
savingFeature.value = false;
}
@@ -901,10 +933,19 @@ async function saveDisplay() {
const res = await saveAdminDisplaySettings({
layoutTitle: adminLayoutTitle.value,
});
// Also save admin language to feature settings as it's a global preference
await saveFeatureSettings({
adminLanguage: adminLanguage.value
});
if (updateSiteTitle) {
updateSiteTitle(adminLayoutTitle.value);
}
// Update local locale
locale.value = adminLanguage.value;
localStorage.setItem('admin_language', adminLanguage.value);
showToast(res.message || "保存成功", "success");
} catch (e: any) {

View File

@@ -1,7 +1,7 @@
<template>
<div class="page">
<div style="display: flex; align-items: center; gap: 20px">
<h2 class="page-title">数据看板</h2>
<h2 class="page-title">{{ t('stats.title') }}</h2>
</div>
<div
v-if="toastVisible"
@@ -13,28 +13,28 @@
<div class="card">
<div class="card-title-row">
<h3 class="card-title">整体概览</h3>
<h3 class="card-title">{{ t('stats.overview') }}</h3>
</div>
<div v-if="statsLoading" class="page-hint">加载中...</div>
<div v-if="statsLoading" class="page-hint">{{ t('common.loading') }}</div>
<div v-else-if="statsError" class="page-error">{{ statsError }}</div>
<div v-else>
<div class="stats-grid">
<div class="stats-item">
<div class="stats-label">总评论数</div>
<div class="stats-label">{{ t('stats.total') }}</div>
<div class="stats-value">{{ statsSummary.total }}</div>
</div>
<div class="stats-item">
<div class="stats-label">已通过</div>
<div class="stats-label">{{ t('stats.approved') }}</div>
<div class="stats-value stats-value-approved">
{{ statsSummary.approved }}
</div>
</div>
<div class="stats-item">
<div class="stats-label">待审核</div>
<div class="stats-label">{{ t('stats.pending') }}</div>
<div class="stats-value stats-value-pending">{{ statsSummary.pending }}</div>
</div>
<div class="stats-item">
<div class="stats-label">已拒绝</div>
<div class="stats-label">{{ t('stats.rejected') }}</div>
<div class="stats-value stats-value-rejected">
{{ statsSummary.rejected }}
</div>
@@ -45,7 +45,7 @@
<div class="card">
<div class="card-title-row">
<h3 class="card-title">评论数趋势</h3>
<h3 class="card-title">{{ t('stats.trend') }}</h3>
<div class="chart-tabs">
<button
class="chart-tab"
@@ -53,7 +53,7 @@
type="button"
@click="changeChartRange('7')"
>
最近 7
{{ t('stats.last7Days') }}
</button>
<button
class="chart-tab"
@@ -61,11 +61,11 @@
type="button"
@click="changeChartRange('30')"
>
最近 30
{{ t('stats.last30Days') }}
</button>
</div>
</div>
<div v-if="statsLoading" class="page-hint">加载中...</div>
<div v-if="statsLoading" class="page-hint">{{ t('common.loading') }}</div>
<div v-else-if="statsError" class="page-error">{{ statsError }}</div>
<div class="chart-wrapper">
<div ref="chartEl" class="chart"></div>
@@ -74,20 +74,20 @@
<div class="card">
<div class="card-title-row">
<h3 class="card-title">按站点统计</h3>
<h3 class="card-title">{{ t('stats.bySite') }}</h3>
</div>
<div v-if="statsLoading" class="page-hint">加载中...</div>
<div v-if="statsLoading" class="page-hint">{{ t('common.loading') }}</div>
<div v-else-if="statsError" class="page-error">{{ statsError }}</div>
<div v-else-if="domainStats.length === 0" class="page-hint">暂无评论数据</div>
<div v-else-if="domainStats.length === 0" class="page-hint">{{ t('stats.noData') }}</div>
<div v-else class="domain-stats-layout">
<div class="domain-table-wrapper">
<div class="domain-table">
<div class="domain-table-header">
<div class="domain-cell domain-cell-domain">域名</div>
<div class="domain-cell">总数</div>
<div class="domain-cell">已通过</div>
<div class="domain-cell">待审核</div>
<div class="domain-cell">已拒绝</div>
<div class="domain-cell domain-cell-domain">{{ t('stats.table.domain') }}</div>
<div class="domain-cell">{{ t('stats.table.total') }}</div>
<div class="domain-cell">{{ t('stats.table.approved') }}</div>
<div class="domain-cell">{{ t('stats.table.pending') }}</div>
<div class="domain-cell">{{ t('stats.table.rejected') }}</div>
</div>
<div
v-for="item in domainStats"
@@ -113,10 +113,13 @@
<script setup lang="ts">
import { onMounted, onBeforeUnmount, ref, nextTick, watch, inject } from "vue";
import type { Ref } from "vue";
import { useI18n } from "vue-i18n";
import * as echarts from "echarts";
import { fetchCommentStats } from "../../api/admin";
import { useSite } from "../../composables/useSite";
const { t } = useI18n();
type DomainStat = {
domain: string;
total: number;