diff --git a/README.md b/README.md
index 2249273a5..f642ac950 100644
--- a/README.md
+++ b/README.md
@@ -28,6 +28,7 @@
+
diff --git a/apps/dashboard/src/app/(dashboard)/settings/_components/LanguageSetting.tsx b/apps/dashboard/src/app/(dashboard)/settings/_components/LanguageSetting.tsx
index bf40409a1..675989bda 100644
--- a/apps/dashboard/src/app/(dashboard)/settings/_components/LanguageSetting.tsx
+++ b/apps/dashboard/src/app/(dashboard)/settings/_components/LanguageSetting.tsx
@@ -2,7 +2,7 @@
/**
* Settings → General → Language. Switches the interface locale via the shared
- * I18nProvider (which flips , so RTL for Arabic is automatic).
+ * I18nProvider (which flips , so RTL for Arabic and Persian is automatic).
* Each language is a selectable card showing its AUTONYM — its own name, in its
* own script (中文, Español, …), never translated into the current UI language.
*/
@@ -16,6 +16,7 @@ import { SettingsSection } from "./SettingsSection";
const NATIVE: Record = {
en: "English",
ar: "العربية",
+ fa: "فارسی",
es: "Español",
fr: "Français",
de: "Deutsch",
@@ -29,6 +30,7 @@ const NATIVE: Record = {
const CODE: Record = {
en: "EN",
ar: "ع",
+ fa: "فا",
es: "ES",
fr: "FR",
de: "DE",
diff --git a/apps/dashboard/src/app/(onboarding)/onboarding/page.tsx b/apps/dashboard/src/app/(onboarding)/onboarding/page.tsx
index d020aef2e..e447e256b 100644
--- a/apps/dashboard/src/app/(onboarding)/onboarding/page.tsx
+++ b/apps/dashboard/src/app/(onboarding)/onboarding/page.tsx
@@ -30,12 +30,12 @@ const MoonIcon = () => (
/** Each language's own name / short glyph, in its own script (never translated). */
const LANG_NATIVE: Record = {
- en: "English", ar: "العربية", es: "Español", fr: "Français",
+ en: "English", ar: "العربية", fa: "فارسی", es: "Español", fr: "Français",
de: "Deutsch", pt: "Português", ja: "日本語", zh: "中文",
tr: "Türkçe",
};
const LANG_CODE: Record = {
- en: "EN", ar: "ع", es: "ES", fr: "FR", de: "DE", pt: "PT", ja: "日", zh: "中",
+ en: "EN", ar: "ع", fa: "فا", es: "ES", fr: "FR", de: "DE", pt: "PT", ja: "日", zh: "中",
tr: "TR",
};
diff --git a/apps/dashboard/src/i18n/i18n-parity.test.ts b/apps/dashboard/src/i18n/i18n-parity.test.ts
index ea3aa3461..0bafeb4a3 100644
--- a/apps/dashboard/src/i18n/i18n-parity.test.ts
+++ b/apps/dashboard/src/i18n/i18n-parity.test.ts
@@ -248,7 +248,7 @@ describe("i18n locale parity vs the English source", () => {
/**
* The check the two above CANNOT make.
*
- * Both of them reason about key PRESENCE. A key that exists in all nine locales
+ * Both of them reason about key PRESENCE. A key that exists in all ten locales
* while still holding the English sentence is invisible to them — the locale is
* "complete", the suite is green, and the UI renders English. That is not
* hypothetical: `verifyEmail.code*` sat like that in 7 of 8 locales, and when the
@@ -266,26 +266,30 @@ describe("i18n locale parity vs the English source", () => {
* A namespace absent from this map must stay at zero.
*/
const UNTRANSLATED_BASELINE: Record = {
- deploy: 133,
- settings: 99,
- projectSettings: 48,
- importProject: 43,
+ // +N on several rows: `fa` keeps product names and machine placeholders
+ // identical to English (Openship Cloud, gh CLI, AWS S3, PEM headers, `npm start`,
+ // `{name}: {label}`), matching ar/tr. Those strings are multi-word so they
+ // count here; they are not leftover UI copy.
+ deploy: 139,
+ settings: 114,
+ projectSettings: 54,
+ importProject: 45,
billing: 28,
- onboarding: 24,
- projectDetail: 19,
- library: 18,
- misc: 18,
+ onboarding: 27,
+ projectDetail: 21,
+ library: 19,
+ misc: 23,
projects: 17,
- deployments: 14,
+ deployments: 16,
emailsAdmin: 12,
overview: 12,
- chrome: 10,
+ chrome: 11,
widgets: 9,
dashboard: 6,
emails: 6,
- servers: 6,
+ servers: 8,
migration: 3,
- jobs: 2,
+ jobs: 3,
// `auth` is deliberately absent, i.e. pinned at 0: it was brought to zero when
// password reset moved from a link to a code, and it is the flow where an
// English-rendering string is most costly — somebody locked out of their account
diff --git a/apps/dashboard/src/i18n/index.ts b/apps/dashboard/src/i18n/index.ts
index 30d3ff563..5ef12ca4e 100644
--- a/apps/dashboard/src/i18n/index.ts
+++ b/apps/dashboard/src/i18n/index.ts
@@ -42,13 +42,13 @@ import autoDns from "./locales/en/autoDns.json";
export const baseDictionary = { brand, auth, dashboard, settings, servers, billing, library, onboarding, deploy, deployments, importProject, projects, projectSettings, projectDetail, emails, emailsAdmin, chrome, overview, widgets, misc, migration, jobs, issues, autoDns };
export type Dictionary = typeof baseDictionary;
-export const locales = ["en", "ar", "es", "fr", "de", "pt", "ja", "zh", "tr"] as const;
+export const locales = ["en", "ar", "fa", "es", "fr", "de", "pt", "ja", "zh", "tr"] as const;
export type Locale = (typeof locales)[number];
export const defaultLocale: Locale = "en";
export const LOCALE_COOKIE = "openship-locale";
/** RTL languages. */
-const rtlLocales = new Set(["ar"]);
+const rtlLocales = new Set(["ar", "fa"]);
export function isRtl(locale: Locale): boolean {
return rtlLocales.has(locale);
}
diff --git a/apps/dashboard/src/i18n/locales/fa/auth.json b/apps/dashboard/src/i18n/locales/fa/auth.json
new file mode 100644
index 000000000..5e4be065e
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/fa/auth.json
@@ -0,0 +1,111 @@
+{
+ "login": {
+ "title": "خوش آمدید",
+ "subtitle": "برای ادامه وارد حساب خود شوید.",
+ "emailLabel": "ایمیل",
+ "emailPlaceholder": "you@example.com",
+ "passwordLabel": "رمز عبور",
+ "passwordPlaceholder": "••••••••",
+ "forgot": "فراموش کردید؟",
+ "submit": "ورود",
+ "submitting": "در حال ورود…",
+ "noAccount": "حساب ندارید؟",
+ "createOne": "یکی بسازید"
+ },
+ "zeroAuth": {
+ "title": "ورود در اینجا در دسترس نیست",
+ "remoteBrowser": "این نمونه بدون حساب کاربری اجرا میشود و نشست فقط برای مرورگر روی همان ماشینی ساخته میشود که Openship روی آن اجراست. داشبورد را از {origin} باز کردهاید، بنابراین چیزی برای ورود وجود ندارد.",
+ "refused": "این نمونه بدون حساب کاربری اجرا میشود و سرور از ساخت نشست برای این مرورگر خودداری کرد؛ بنابراین ورود در اینجا کامل نمیشود.",
+ "remedy": "برای ورود با حساب، این را به محیط Openship اضافه کنید و آن را دوباره راهاندازی کنید:",
+ "remedyAdmin": "سپس با حساب مدیر ساختهشده هنگام نصب وارد شوید.",
+ "retry": "تلاش دوباره"
+ },
+ "register": {
+ "title": "حساب بسازید",
+ "subtitle": "در چند ثانیه با Openship شروع کنید.",
+ "nameLabel": "نام",
+ "namePlaceholder": "نام شما",
+ "emailLabel": "ایمیل",
+ "emailPlaceholder": "you@example.com",
+ "passwordLabel": "رمز عبور",
+ "passwordPlaceholder": "حداقل ۸ نویسه",
+ "submit": "ساخت حساب",
+ "submitting": "در حال ساخت حساب…",
+ "hasAccount": "از قبل حساب دارید؟",
+ "signIn": "ورود"
+ },
+ "forgotPassword": {
+ "title": "بازنشانی رمز عبور",
+ "subtitle": "ایمیل خود را وارد کنید تا یک کد بازنشانی برایتان بفرستیم.",
+ "emailLabel": "ایمیل",
+ "emailPlaceholder": "you@example.com",
+ "submit": "ارسال کد بازنشانی",
+ "submitting": "در حال ارسال…",
+ "backToSignIn": "بازگشت به ورود",
+ "spamHint": "نمیبینید؟ پوشه هرزنامه یا هرزنامهها را بررسی کنید."
+ },
+ "resetPassword": {
+ "title": "رمز عبور تازه بگذارید",
+ "subtitle": "یک رمز قوی برای حساب خود انتخاب کنید.",
+ "codeSentTo": "یک کد ۶ رقمی به {email} ایمیل کردیم. آن را همراه رمز تازه در زیر وارد کنید.",
+ "codeSentGeneric": "کد بازنشانی را که ایمیل کردیم همراه رمز تازه وارد کنید.",
+ "passwordLabel": "رمز عبور تازه",
+ "passwordPlaceholder": "حداقل ۸ نویسه",
+ "confirmLabel": "تأیید رمز عبور",
+ "confirmPlaceholder": "رمز را دوباره وارد کنید",
+ "submit": "بازنشانی رمز عبور",
+ "submitting": "در حال بازنشانی…",
+ "backToSignIn": "بازگشت به ورود",
+ "doneTitle": "رمز بازنشانی شد",
+ "doneDescription": "رمز عبور شما با موفقیت بهروز شد.",
+ "doneAction": "ورود"
+ },
+ "verifyEmail": {
+ "verifyingTitle": "در حال تأیید ایمیل شما…",
+ "verifyingSubtitle": "یک لحظه.",
+ "verifiedTitle": "ایمیل تأیید شد",
+ "verifiedDescription": "ایمیل شما تأیید شد. اکنون میتوانید وارد شوید.",
+ "verifiedAction": "ادامه برای ورود",
+ "errorTitle": "تأیید ناموفق بود",
+ "errorDescription": "ممکن است کد نادرست یا منقضی شده باشد.",
+ "resendEmail": "ارسال دوباره کد",
+ "signIn": "ورود",
+ "pendingTitle": "ایمیل خود را تأیید کنید",
+ "pendingSentTo": "یک کد ۶ رقمی به {email} ایمیل کردیم. آن را در زیر وارد کنید تا ایمیل تأیید شود.",
+ "pendingGeneric": "کد تأییدی را که ایمیل کردیم وارد کنید.",
+ "resendVerification": "ارسال دوباره کد",
+ "backToSignIn": "بازگشت به ورود",
+ "codeLabel": "کد تأیید",
+ "codePlaceholder": "کد ۶ رقمی را وارد کنید",
+ "verifyCode": "تأیید ایمیل",
+ "codeLocked": "تلاشهای زیاد — این کد قفل شد. کد تازهای بخواهید و ایمیل را بررسی کنید.",
+ "codeExpired": "این کد منقضی شده است. یک کد تازه بخواهید.",
+ "codeInvalid": "کد نادرست است. دوباره بررسی کنید.",
+ "resendRateLimited": "لطفاً کمی صبر کنید و بعد کد دیگری بخواهید.",
+ "spamHint": "کد را نمیبینید؟ پوشه هرزنامه را بررسی کنید."
+ },
+ "oauth": {
+ "or": "یا",
+ "github": "ادامه با GitHub",
+ "google": "ادامه با Google"
+ },
+ "errors": {
+ "invalidCredentials": "ایمیل یا رمز عبور نادرست است.",
+ "serverUnreachable": "به سرور نرسیدیم. آیا API در حال اجراست؟",
+ "generic": "مشکلی پیش آمد. دوباره تلاش کنید.",
+ "oauthFailed": "ورود OAuth ناموفق بود. دوباره تلاش کنید.",
+ "passwordMin": "رمز عبور باید حداقل ۸ نویسه باشد.",
+ "passwordMismatch": "رمزها یکسان نیستند.",
+ "invalidToken": "کد بازنشانی نامعتبر یا موجود نیست. کد تازهای بخواهید.",
+ "resetFailed": "بازنشانی رمز ممکن نشد.",
+ "createFailed": "ساخت حساب ممکن نشد.",
+ "verificationFailed": "تأیید ناموفق بود.",
+ "verificationExpired": "تأیید ناموفق بود. ممکن است کد منقضی شده باشد.",
+ "resendFailed": "ارسال دوباره ناموفق بود. دوباره تلاش کنید.",
+ "verificationSent": "ایمیل تأیید ارسال شد!"
+ },
+ "back": "بازگشت",
+ "toggleTheme": "تغییر پوسته",
+ "showPassword": "نمایش رمز عبور",
+ "hidePassword": "پنهان کردن رمز عبور"
+}
diff --git a/apps/dashboard/src/i18n/locales/fa/autoDns.json b/apps/dashboard/src/i18n/locales/fa/autoDns.json
new file mode 100644
index 000000000..d8af15c2e
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/fa/autoDns.json
@@ -0,0 +1,29 @@
+{
+ "checking": "در حال بررسی ارائهدهنده DNS شما…",
+ "noProviderTitle": "این رکوردها را بهصورت خودکار تنظیم کنید",
+ "noProviderDesc": "یک ارائهدهنده DNS متصل کنید تا Openship این رکوردها را برایتان اضافه کند.",
+ "connect": "اتصال ارائهدهنده DNS",
+ "unauthorizedTitle": "ارائهدهنده DNS نیاز به اتصال دوباره دارد",
+ "unauthorizedDesc": "توکن ذخیرهشده رد شد. برای پیکربندی خودکار DNS دوباره متصلش کنید.",
+ "reconnect": "اتصال دوباره ارائهدهنده",
+ "unavailableTitle": "به ارائهدهنده DNS نرسیدیم",
+ "unavailableDesc": "الان نتوانستیم زون شما را بررسی کنیم. کمی بعد دوباره تلاش کنید.",
+ "retry": "تلاش دوباره",
+ "managedBy": "{provider} مدیریت {zone} را بر عهده دارد",
+ "apply": "پیکربندی خودکار DNS",
+ "applying": "در حال پیکربندی…",
+ "reapply": "اعمال دوباره رکوردها",
+ "allInPlace": "همه رکوردها در جای خود هستند",
+ "actionCreate": "افزودن",
+ "actionUpdate": "بهروزرسانی",
+ "actionAdopt": "تغییر اشاره",
+ "actionInSync": "در جای خود",
+ "actionConflict": "تداخل",
+ "conflictNote": "توسط Openship مدیریت نمیشود — آن را حذف کنید یا این رکورد را دستی تنظیم کنید.",
+ "outcomeApplied": "اعمال شد",
+ "outcomeSkipped": "رد شد",
+ "outcomeFailed": "ناموفق",
+ "resultDone": "DNS بهروز شد.",
+ "resultPartial": "برخی رکوردها نیاز به توجه دارند.",
+ "applyFailed": "پیکربندی DNS ممکن نشد."
+}
diff --git a/apps/dashboard/src/i18n/locales/fa/billing.json b/apps/dashboard/src/i18n/locales/fa/billing.json
new file mode 100644
index 000000000..7dc38601f
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/fa/billing.json
@@ -0,0 +1,203 @@
+{
+ "capacity": {
+ "title": "ظرفیت و مصرف",
+ "subtitle": "ظرفیت گنجاندهشده در طرح شما و میزان مصرف. ارقام زنده از Openship Cloud همگام میشوند.",
+ "routes": "مسیرهای رایگان",
+ "runningServices": "سرویسهای در حال اجرا",
+ "projects": "پروژهها",
+ "workspaces": "فضاهای کار",
+ "unlimited": "نامحدود",
+ "used": "مصرفشده",
+ "of": "از {max}",
+ "syncing": "همگامسازی از ابر"
+ },
+ "topupsComingSoon": "شارژ یکباره اعتبار بهزودی میآید — میتوانید اینجا اعتبار را روی سهمیه ماهانه اضافه کنید.",
+ "header": {
+ "credits": "اعتبار",
+ "creditsSuffix": "از {limit}",
+ "overQuota": "بیش از سهمیه",
+ "bandwidth": "پهنای باند",
+ "bandwidthNote": "ورودی + خروجی مجموع",
+ "build": "زمان ساخت",
+ "min": "دقیقه",
+ "capacity": "ظرفیت",
+ "workspaces": "فضای کار",
+ "vcpus": "vCPU",
+ "ram": "RAM",
+ "diskCap": "دیسک"
+ },
+ "tabs": {
+ "overview": "نمای کلی",
+ "usage": "مصرف",
+ "plans": "طرحها",
+ "topups": "شارژ",
+ "payment": "روش پرداخت",
+ "invoices": "فاکتورها"
+ },
+ "portal": {
+ "openButton": "باز کردن پورتال Stripe",
+ "errorMissingUrl": "URL پورتال در پاسخ نبود",
+ "errorOpenFailed": "باز کردن پورتال Stripe ناموفق بود"
+ },
+ "unavailable": {
+ "notConnected": {
+ "title": "برای مدیریت صورتحساب به Openship Cloud متصل شوید",
+ "description": "نمونه محلی شما هنوز به Openship Cloud متصل نیست. برای دیدن وضعیت اشتراک، مصرف، فاکتورها و شارژ متصل شوید.",
+ "connect": "اتصال به Openship Cloud",
+ "connecting": "در انتظار ورود…"
+ },
+ "sessionExpired": {
+ "title": "نشست Openship Cloud منقضی شد",
+ "description": "هنوز پیوند دارید، اما توکن نشستی که Openship Cloud صادر کرده دیگر معتبر نیست. برای تازهسازی دوباره متصل شوید — نیازی به قطع اتصال قبلی نیست.",
+ "reconnect": "اتصال دوباره به Openship Cloud",
+ "connecting": "در انتظار ورود…"
+ },
+ "unreachable": {
+ "title": "به صورتحساب Openship Cloud نرسیدیم",
+ "description": "ممکن است نشست ابری منقضی شده باشد یا ابر موقتاً در دسترس نباشد. دوباره تلاش کنید، یا از تنظیمات → Cloud دوباره متصل شوید.",
+ "tryAgain": "تلاش دوباره",
+ "reconnect": "اتصال دوباره"
+ },
+ "notEnabled": {
+ "title": "صورتحساب برای این سازمان فعال نیست",
+ "description": "صورتحساب پیکربندی شده اما برای این سازمان فعال نیست. برای فعالسازی با مدیر تماس بگیرید."
+ }
+ },
+ "plansRoute": {
+ "loadError": "بارگذاری طرحها ناموفق بود",
+ "genericError": "مشکلی پیش آمد",
+ "tryAgain": "تلاش دوباره",
+ "checkoutError": "شروع پرداخت ناموفق بود"
+ },
+ "pricing": {
+ "custom": "سفارشی",
+ "comingSoon": "بهزودی",
+ "mostPopular": "محبوبترین",
+ "currentPlan": "طرح فعلی",
+ "contactSales": "تماس با فروش",
+ "freeForever": "همیشه رایگان",
+ "upgrade": "ارتقا"
+ },
+ "topups": {
+ "loadError": "بارگذاری بستههای شارژ ناموفق بود",
+ "title": "شارژ اعتبار",
+ "description": "بستههای اعتبار یکباره که از طریق Stripe دریافت میشوند. اعتبار هرگز منقضی نمیشود و روی سهمیه ماهانه انباشته میشود.",
+ "tryAgain": "تلاش دوباره",
+ "credits": "اعتبار",
+ "redirecting": "در حال هدایت…",
+ "buy": "خرید",
+ "empty": "الان بسته شارژی موجود نیست.",
+ "receiptsTitle": "شارژهای اخیر",
+ "receiptsDescription": "رسیدها در Stripe مدیریت میشوند — پورتال صورتحساب را باز کنید تا تاریخچه فاکتور را ببینید و رسیدها را دانلود کنید.",
+ "opening": "در حال باز کردن…",
+ "openPortal": "باز کردن پورتال Stripe",
+ "checkoutError": "شروع پرداخت ناموفق بود",
+ "portalError": "باز کردن پورتال صورتحساب ناموفق بود"
+ },
+ "overview": {
+ "creditsLeft": "اعتبار باقیمانده",
+ "planLabel": "طرح {name}",
+ "usedThisPeriod": "مصرف این دوره",
+ "resets": "بازنشانی",
+ "resetsInDay": "تا {n} روز دیگر",
+ "resetsInDays": "تا {n} روز دیگر",
+ "none": "—",
+ "upgradeToPro": "ارتقا به Pro",
+ "recentActivity": "فعالیت اخیر",
+ "last7Days": "۷ روز گذشته",
+ "viewFullUsage": "مشاهده مصرف کامل",
+ "noUsageYet": "هنوز مصرفی نیست",
+ "usageError": "بارگذاری مصرف ناموفق بود",
+ "needMoreCredits": "اعتبار بیشتری نیاز دارید؟",
+ "oneTimeTopups": "شارژ یکباره، بدون تغییر اشتراک",
+ "seeAllPacks": "مشاهده همه بستهها",
+ "packsError": "بارگذاری بستهها ناموفق بود",
+ "noPacks": "بسته شارژی موجود نیست.",
+ "creditsAmount": "{n} اعتبار",
+ "oneTime": "{price} یکباره",
+ "buy": "خرید",
+ "checkoutError": "شروع پرداخت ناموفق بود"
+ },
+ "usage": {
+ "loadError": "بارگذاری مصرف ناموفق بود",
+ "kpi": {
+ "balance": "مانده",
+ "balanceSuffix": "از {limit} اعتبار",
+ "consumed": "مصرف این دوره",
+ "credits": "اعتبار",
+ "burnRate": "نرخ مصرف",
+ "burnRateSuffix": "اعتبار/روز (۷ روز اخیر)",
+ "projected": "پیشبینی پایان دوره",
+ "noPeriod": "دورهای تنظیم نشده"
+ },
+ "granularity": {
+ "day": "روز",
+ "week": "هفته"
+ },
+ "chart": {
+ "title": "مصرف در طول زمان",
+ "subtitle": "واحدهای اندازهگیریشده انباشته بهازای هر {unit}."
+ },
+ "breakdown": {
+ "title": "تفکیک منابع",
+ "subtitle": "اعتبار بهتناسب مصرف منابع اندازهگیریشده در بازه انتخابی نسبت داده شده است.",
+ "resource": "منبع",
+ "units": "واحد",
+ "credits": "اعتبار",
+ "percentOfTotal": "٪ از کل",
+ "loading": "در حال بارگذاری…",
+ "empty": "در بازه انتخابی مصرفی نیست."
+ },
+ "resources": {
+ "cpu": { "label": "زمان CPU", "units": "ساعت-vCPU" },
+ "memory": { "label": "حافظه", "units": "ساعت-GB" },
+ "disk": { "label": "IO دیسک", "units": "GB" },
+ "network": { "label": "شبکه", "units": "GB" }
+ },
+ "toolbar": {
+ "workspace": "فضای کار",
+ "workspaceTooltip": "تفکیک بهازای فضای کار در v1.1 میآید",
+ "allWorkspaces": "همه فضاهای کار",
+ "to": "تا"
+ },
+ "empty": "برای بازه انتخابی داده مصرفی نیست."
+ },
+ "chart": {
+ "cpu": "CPU",
+ "memory": "حافظه",
+ "diskIo": "IO دیسک",
+ "network": "شبکه",
+ "weekTick": "هفته {n}"
+ },
+ "layout": {
+ "title": "صورتحساب و مصرف",
+ "subtitle": "اشتراک، مصرف و روشهای پرداخت را مدیریت کنید."
+ },
+ "sidebar": {
+ "planSuffix": "طرح {name}",
+ "status": "وضعیت",
+ "statusInactive": "غیرفعال",
+ "upgradeTo": "ارتقا به {name}",
+ "contactSales": "تماس با فروش",
+ "freeForever": "همیشه رایگان",
+ "perMonth": "${price}/ماه",
+ "statuses": {
+ "active": "فعال",
+ "trialing": "آزمایشی",
+ "past_due": "سررسید گذشته",
+ "canceled": "لغوشده",
+ "incomplete": "ناتمام",
+ "incomplete_expired": "منقضی",
+ "unpaid": "پرداختنشده",
+ "paused": "متوقف"
+ }
+ },
+ "paymentPanel": {
+ "title": "روش پرداخت",
+ "description": "کارتها، حسابهای بانکی و جزئیات صورتحساب در پورتال مشتری Stripe مدیریت میشوند. پورتال را باز کنید تا روشهای پرداخت را اضافه، حذف یا بهروز کنید."
+ },
+ "invoicesPanel": {
+ "title": "فاکتورها",
+ "description": "فاکتورها و رسیدها در پورتال مشتری Stripe هستند. پورتال را باز کنید تا تاریخچه صورتحساب را ببینید و فاکتورهای PDF را دانلود کنید."
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/fa/brand.json b/apps/dashboard/src/i18n/locales/fa/brand.json
new file mode 100644
index 000000000..5dc399b1c
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/fa/brand.json
@@ -0,0 +1 @@
+"اوپنشیپ"
diff --git a/apps/dashboard/src/i18n/locales/fa/chrome.json b/apps/dashboard/src/i18n/locales/fa/chrome.json
new file mode 100644
index 000000000..42d3c4b04
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/fa/chrome.json
@@ -0,0 +1,86 @@
+{
+ "accountSwitcher": {
+ "workspacesCount": "{count} فضای کار",
+ "newWorkspace": "فضای کار جدید",
+ "createTitle": "ایجاد فضای کار",
+ "createDescription": "سازمانی جداگانه برای پروژهها، استقرارها، سرورها و اعضا.",
+ "nameLabel": "نام",
+ "namePlaceholder": "Acme Corp",
+ "cancel": "لغو",
+ "create": "ایجاد",
+ "toastTitle": "سازمان",
+ "switchFailed": "تغییر ناموفق بود",
+ "createFailed": "ایجاد ناموفق بود"
+ },
+ "sidebar": {
+ "workspaceFallback": "فضای کار",
+ "workspacesCount": "{count} فضای کار",
+ "switchOrganization": "تغییر سازمان",
+ "current": "فعلی",
+ "cloudLabel": "ابر: {email}",
+ "linkedToCloud": "متصل به Openship Cloud با {email}",
+ "backToSetup": "بازگشت به راهاندازی",
+ "personal": "شخصی"
+ },
+ "mailRail": {
+ "selectServer": "یک سرور ایمیل انتخاب کنید",
+ "switchServer": "تغییر سرور ایمیل",
+ "setupIncomplete": "راهاندازی ناقص است"
+ },
+ "migration": {
+ "inProgressTitle": "مهاجرت در حال انجام است",
+ "inProgressBody": "دادههای شما در حال انتقال است. این صفحه پس از پایان جابهجایی تازهسازی میشود — معمولاً ۳۰ تا ۹۰ ثانیه.",
+ "inProgressHint": "این زبانه را نبندید. وضعیت مهاجرت را هر چند ثانیه بررسی میکنیم و بهصورت خودکار بارگذاری دوباره میکنیم.",
+ "newLocation": "محل جدید",
+ "switchBackPrompt": "حالت تککاربره را روی این ماشین میخواهید؟",
+ "switchBackAction": "بازگشت به حالت تککاربره",
+ "cloudTitle": "این نمونه به Openship Cloud منتقل شد",
+ "cloudBody": "تیم شما اکنون در URL زیر همکاری میکند. این نمونه محلی دیگر پروژهها، تنظیمات یا استقرارهای شما را ندارد — برای ادامه، داشبورد ابری را باز کنید.",
+ "cloudCta": "باز کردن داشبورد ابری",
+ "tunneledTitle": "Openship شما اکنون از طریق تونل در دسترس است",
+ "tunneledBody": "همتیمیها وقتی این ماشین آنلاین باشد میتوانند در URL زیر وارد شوند. داده همچنان روی این دستگاه است — تونل فقط آن را از طریق لبه Oblien در دسترس میگذارد.",
+ "tunneledCta": "باز کردن URL تونل",
+ "serverTitle": "این نمونه به سرور شما منتقل شد",
+ "serverBody": "تیم شما اکنون در URL زیر همکاری میکند. این نمونه محلی دیگر پروژهها، تنظیمات یا استقرارهای شما را ندارد — برای ادامه، داشبورد تیم را باز کنید.",
+ "serverCta": "باز کردن داشبورد تیم"
+ },
+ "notFound": {
+ "title": "صفحه پیدا نشد",
+ "descGlobal": "صفحهای که میجویید وجود ندارد یا جابهجا شده است.",
+ "descDashboard": "این صفحه وجود ندارد یا شاید جابهجا شده باشد. URL را بررسی کنید، یا به داشبورد برگردید.",
+ "backToDashboard": "بازگشت به داشبورد",
+ "viewDeployments": "مشاهده استقرارها",
+ "copyId": "کپی ID"
+ },
+ "error": {
+ "title": "این صفحه با خطا روبهرو شد",
+ "description": "چیزی در این صفحه درست نمایش داده نشد. معمولاً تلاش دوباره مشکل را برطرف میکند — هیچکدام از استقرارهای شما آسیب ندیده است.",
+ "tryAgain": "تلاش دوباره",
+ "goToProjects": "رفتن به پروژهها",
+ "refCode": "Ref"
+ },
+ "apiDown": {
+ "title": "به API دسترسی نیست",
+ "description": "داشبورد در حال اجراست، اما نمیتواند با Openship API صحبت کند — بنابراین هنوز چیزی برای نمایش نیست. استقرارهای شما در هر صورت به کار خود ادامه میدهند.",
+ "hintStatus": "مطمئن شوید API بالا است: روی میزبان `openship status` را اجرا کنید.",
+ "hintStart": "در حال اجرا نیست؟ با `openship up` آن را شروع کنید.",
+ "hintRemote": "یک ماشین راه دور را مدیریت میکنید؟ URL آن و در دسترس بودن پورت API از اینجا را تأیید کنید.",
+ "retry": "تلاش دوباره",
+ "docs": "مستندات میزبانی شخصی"
+ },
+ "errorLinks": {
+ "docs": "مستندات",
+ "github": "گزارش مشکل"
+ },
+ "window": {
+ "minimize": "کوچککردن",
+ "maximize": "بزرگکردن",
+ "restore": "بازگردانی",
+ "close": "بستن",
+ "navigation": "ناوبری",
+ "back": "بازگشت",
+ "forward": "جلو",
+ "reload": "بارگذاری دوباره",
+ "devTools": "ابزارهای توسعهدهنده"
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/fa/dashboard.json b/apps/dashboard/src/i18n/locales/fa/dashboard.json
new file mode 100644
index 000000000..c481942f6
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/fa/dashboard.json
@@ -0,0 +1,221 @@
+{
+ "home": {
+ "goodMorning": "صبح بخیر",
+ "goodAfternoon": "ظهر بخیر",
+ "goodEvening": "عصر بخیر",
+ "subtitle": "آنچه در پروژههای شما میگذرد",
+ "statsProjects": "پروژهها",
+ "statsDeployments": "استقرارها",
+ "statsSuccessful": "موفق",
+ "statsFailed": "ناموفق",
+ "recentProjects": "پروژههای اخیر",
+ "yourLatestActivity": "آخرین فعالیت شما",
+ "viewAll": "مشاهده همه",
+ "noProjectsYet": "هنوز پروژهای نیست",
+ "createFirstProject": "اولین پروژه خود را بسازید",
+ "justNow": "همین الان",
+ "minutesAgo": "{n} دقیقه پیش",
+ "hoursAgo": "{n} ساعت پیش",
+ "daysAgo": "{n} روز پیش",
+ "monthsAgo": "{n} ماه پیش",
+ "statusLive": "زنده",
+ "statusPaused": "متوقف",
+ "yourProjects": "پروژههای شما",
+ "loading": "در حال بارگذاری...",
+ "projectCountOne": "{count} پروژه",
+ "projectCountOther": "{count} پروژه",
+ "viewAllProjects": "مشاهده همه {count} پروژه →",
+ "viewAllApps": "مشاهده همه {count} اپ →",
+ "greetingName": "{greeting}، {name}",
+ "importGit": "وارد کردن Git",
+ "importGitDesc": "از مخزن",
+ "mcpDeploy": "استقرار MCP",
+ "mcpDeployDesc": "اتصال یک عامل هوش مصنوعی",
+ "settingsCard": "تنظیمات",
+ "settingsCardDesc": "حساب و تیم",
+ "docs": "مستندات",
+ "docsDesc": "بیشتر بدانید",
+ "activityTitle": "فعالیت",
+ "systemStatus": "وضعیت سیستم",
+ "operational": "عملیاتی",
+ "oneIssue": "۱ مشکل",
+ "manyIssues": "{n} مشکل",
+ "recentTitle": "اخیر",
+ "activityEmpty": "فعالیت شما اینجا ظاهر میشود",
+ "appsEmptyTitle": "هنوز اپی نیست",
+ "appsEmptyDesc": "با یک کلیک، کاملاً روی زیرساخت خودتان — پایگاهداده، خودکارسازی، تحلیل، خزانه رمز و بیشتر، روی سرورهای خودتان.",
+ "allSystemsOperational": "همه سیستمها عملیاتیاند",
+ "runningSmoothly": "پروژههای شما بهخوبی اجرا میشوند",
+ "time": {
+ "justNow": "همین الان",
+ "minutes": "{n} دقیقه پیش",
+ "hours": "{n} ساعت پیش",
+ "days": "{n} روز پیش",
+ "months": "{n} ماه پیش"
+ }
+ },
+ "nav": {
+ "home": "خانه",
+ "new-project": "پروژه جدید",
+ "projects": "پروژهها",
+ "apps": "اپها",
+ "deployments": "استقرارها",
+ "issues": "مشکلات",
+ "backups": "پشتیبانها",
+ "domains": "دامنهها",
+ "monitoring": "پایش",
+ "servers": "سرورها",
+ "emails": "ایمیلها",
+ "jobs": "کارها",
+ "settings": "تنظیمات",
+ "billing": "صورتحساب و مصرف",
+ "audit": "گزارش حسابرسی",
+ "mailSetup": "راهاندازی ایمیل",
+ "mailSetupProgress": "پیشرفت راهاندازی",
+ "addMailbox": "افزودن صندوق نامه",
+ "webmail": "وبمیل",
+ "sections": {
+ "main": "اصلی",
+ "settings": "تنظیمات",
+ "infrastructure": "زیرساخت",
+ "mail": "ایمیل",
+ "delivery": "تحویل",
+ "system": "سیستم",
+ "account": "حساب"
+ }
+ },
+ "sidebar": {
+ "expand": "گسترش نوار کناری",
+ "collapse": "جمع کردن نوار کناری"
+ },
+ "user": {
+ "logout": "خروج",
+ "loggingOut": "در حال خروج…",
+ "profile": "نمایه",
+ "preferences": "ترجیحات"
+ },
+ "pages": {
+ "apps": {
+ "title": "اپها",
+ "description": "سرویسهای مدیریتشده با یک کلیک — Convex، WordPress، ایمیل و بیشتر را نصب و اجرا کنید.",
+ "createButton": "نصب اپ",
+ "loading": "در حال بارگذاری اپها…",
+ "countOne": "{count} اپ نصب شده",
+ "countOther": "{count} اپ نصب شده",
+ "emptyTitle": "اولین اپ خود را نصب کنید",
+ "emptyDescription": "Convex، n8n، Ghost و بیشتر را بالا بیاورید — یک کلیک، کاملاً روی زیرساخت خودتان. یکی را برای شروع انتخاب کنید.",
+ "popular": "اپهای محبوب",
+ "alsoDeploy": "همچنین میتوانید مستقر کنید",
+ "browseAll": "مرور همه اپها",
+ "moreApps": "+{count} مورد دیگر",
+ "catalogTitle": "نصب یک اپ",
+ "catalogDescription": "سرویسهای مدیریتشده با یک کلیک. یکی را انتخاب کنید تا بهعنوان اپ نصب شود.",
+ "catalogSearchPlaceholder": "جستجوی اپها…",
+ "catalogNoResults": "هیچ اپی با جستجوی شما مطابقت ندارد.",
+ "categories": {
+ "all": "همه",
+ "backend": "بکاند",
+ "database": "پایگاهداده",
+ "cms": "CMS",
+ "analytics": "تحلیل",
+ "automation": "خودکارسازی",
+ "mail": "ایمیل",
+ "other": "ابزارها"
+ },
+ "installing": "در حال نصب…",
+ "install": "نصب",
+ "cancel": "لغو",
+ "nameLabel": "نام",
+ "configTitle": "پیکربندی {app}",
+ "comingSoon": "بهزودی"
+ },
+ "projects": {
+ "title": "پروژهها",
+ "description": "برنامههای خود را بهسادگی مستقر و مدیریت کنید",
+ "createButton": "ایجاد پروژه",
+ "stats": {
+ "totalProjects": "کل پروژهها",
+ "liveNow": "اکنون زنده",
+ "avgPerDay": "میانگین در روز",
+ "successRate": "نرخ موفقیت"
+ },
+ "searchPlaceholder": "جستجوی پروژهها...",
+ "pinnedProjects": "پروژههای سنجاقشده",
+ "otherProjects": "سایر پروژهها",
+ "noResultsFound": "هیچ پروژهای مطابق «{query}» پیدا نشد",
+ "emptyState": {
+ "title": "اولین پروژه خود را شروع کنید",
+ "description": "یک مخزن Git وارد کنید، از قالب استفاده کنید، یا از CLI مستقر کنید. پروژههای شما اینجا ظاهر میشوند.",
+ "createProject": "ایجاد پروژه",
+ "browseTemplates": "مرور قالبها",
+ "zeroConfig": "استقرار بدون پیکربندی",
+ "instant": "آنی",
+ "instantDesc": "استقرار در چند ثانیه",
+ "global": "سراسری",
+ "globalDesc": "بیش از ۱۰۰ منطقه لبه",
+ "previews": "پیشنمایشها",
+ "previewsDesc": "یک URL برای هر شاخه",
+ "rollbacks": "بازگردانی",
+ "rollbacksDesc": "بازگشت با یک کلیک",
+ "commandPalette": "یا {key} را بزنید تا پالت فرمان باز شود"
+ },
+ "view": {
+ "label": "نما",
+ "list": "فهرست",
+ "grid": "شبکه"
+ }
+ },
+ "deployments": {
+ "title": "استقرارها",
+ "description": "تاریخچه استقرار را ببینید و مدیریت کنید."
+ },
+ "domains": {
+ "title": "دامنهها",
+ "description": "دامنههای سفارشی و رکوردهای DNS را مدیریت کنید."
+ },
+ "monitoring": {
+ "title": "پایش",
+ "description": "لاگها، متریکها و بررسی سلامت سرویسهای شما."
+ },
+ "servers": {
+ "title": "سرورها",
+ "description": "سرورهای استقرار و زیرساخت خود را مدیریت کنید."
+ },
+ "settings": {
+ "title": "تنظیمات",
+ "description": "تنظیمات حساب، تیم و پلتفرم."
+ },
+ "billing": {
+ "title": "صورتحساب و مصرف",
+ "description": "اشتراک، مصرف و روشهای پرداخت را مدیریت کنید."
+ }
+ },
+ "quickLinks": {
+ "title": "اقدامات سریع",
+ "subtitle": "به کارهای رایج بروید",
+ "deployProject": {
+ "title": "استقرار پروژه",
+ "description": "وارد کردن از Git"
+ },
+ "projects": {
+ "title": "پروژهها",
+ "description": "همه پروژهها"
+ },
+ "deployments": {
+ "title": "استقرارها",
+ "description": "تاریخچه ساخت"
+ },
+ "settings": {
+ "title": "تنظیمات",
+ "description": "مدیریت دسترسی"
+ },
+ "domains": {
+ "title": "دامنهها",
+ "description": "دامنههای سفارشی"
+ },
+ "billing": {
+ "title": "صورتحساب و مصرف",
+ "description": "مصرف و طرحها"
+ }
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/fa/deploy.json b/apps/dashboard/src/i18n/locales/fa/deploy.json
new file mode 100644
index 000000000..bf91b0511
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/fa/deploy.json
@@ -0,0 +1,316 @@
+{
+ "page": {
+ "projectNameLabel": "نام پروژه",
+ "projectNameHint": "شناسهای یکتا برای استقرار شما",
+ "uploadedFolder": "پوشهٔ آپلودشده",
+ "errorInvalidUrlTitle": "URL مخزن نامعتبر است",
+ "errorInvalidUrlDetails": "قالب URL مخزن شناخته نشد. URL را بررسی کنید و دوباره تلاش کنید.",
+ "errorLoadProjectTitle": "بارگذاری پروژه ناموفق بود",
+ "errorLoadRepoTitle": "بارگذاری مخزن ناموفق بود",
+ "errorScanFolderFailed": "نتوانستیم این پوشه را اسکن کنیم. مطمئن شوید مسیر درست است و در دسترس است.",
+ "errorLoadRepoFailed": "نتوانستیم این مخزن را بارگذاری کنیم. ممکن است خصوصی باشد، وجود نداشته باشد، یا به آن دسترسی نداشته باشید."
+ },
+ "header": {
+ "private": "خصوصی",
+ "public": "عمومی",
+ "help": "راهنما"
+ },
+ "domainSettings": {
+ "saveFailed": "ذخیرهٔ دامنهها ناموفق بود",
+ "toastTitle": "دامنهها",
+ "routeFreeLabel": "زیردامنهٔ رایگان",
+ "routeFreeDesc": "یک URL رایگان {domain}، آمادهٔ فوری.",
+ "routeCustomLabel": "دامنهٔ اختصاصی",
+ "routeCustomDesc": "دامنهٔ خودتان را به این استقرار وصل کنید.",
+ "routeNoneLabel": "هیچکدام",
+ "routeNoneDesc": "بدون URL عمومی — فقط داخلی. از طریق پورت روی میزبان در دسترس است."
+ },
+ "dns": {
+ "title": "پیکربندی DNS",
+ "addRecordsFor": "این رکوردها را در ارائهدهندهٔ DNS خود برای این دامنه اضافه کنید",
+ "selfInfoPre": "رکورد ",
+ "recordA": "A",
+ "selfInfoMid": " را به IP سرور خود اشاره دهید — HTTPS در اولین استقرار بهصورت خودکار صادر میشود.",
+ "recordTxt": "رکورد TXT",
+ "verifySuffix": " برای تأیید مالکیت.",
+ "cloudInfoPre": "رکورد ",
+ "recordCname": "CNAME",
+ "cloudInfoMid": " را برای مسیریابی اضافه کنید، سپس ",
+ "modalTitle": "دامنه را اشاره دهید، سپس استقرار کنید",
+ "modalSubtitle": "پس از اشارهٔ دامنه به اینجا، HTTPS در اولین استقرار صادر میشود — یا رد شوید و DNS را بعداً از زبانهٔ دامنهها اضافه کنید.",
+ "autoConfigTitle": "پیکربندی خودکار با ارائهدهندهٔ DNS من",
+ "autoConfigDesc": "ثبتکنندهٔ دامنه را وصل کنید تا این رکوردها را برایتان اضافه کنیم.",
+ "comingSoon": "بهزودی",
+ "deployAction": "استقرار",
+ "cancel": "لغو",
+ "loadingRecords": "در حال بارگذاری رکوردهای DNS…",
+ "serverIpPlaceholder": "IP عمومی سرور شما",
+ "record": {
+ "purposeA": "{hostname} را به آدرس IP این سرور اشاره میدهد",
+ "purposeCname": "{hostname} را از طریق شبکهٔ لبه مسیریابی میکند",
+ "purposeCnameAlias": "{hostname} را به {target} اشاره میدهد",
+ "purposeTxt": "مالکیت {hostname} را ثابت میکند — هنگام زدن تأیید، این رکورد را میخوانیم",
+ "fieldType": "نوع",
+ "fieldName": "نام",
+ "fieldValue": "مقدار",
+ "fieldTtl": "TTL",
+ "ttlValue": "خودکار (یا ۳۶۰۰)",
+ "apexHint": "«@» خود دامنه است — بعضی ارائهدهندهها میخواهند این فیلد خالی بماند",
+ "fullName": "نام کامل، اگر ارائهدهنده به آن نیاز دارد:",
+ "proxyNote": "هر سوییچ پروکسی/CDN را خاموش بگذارید (Cloudflare: ابر خاکستری، «فقط DNS») تا بتوان برای این دامنه HTTPS صادر کرد.",
+ "copy": "کپی"
+ }
+ },
+ "buildSummary": {
+ "title": "خلاصهٔ استقرار",
+ "domain": "دامنه",
+ "extraMore": " +{count} مورد دیگر",
+ "services": "سرویسها",
+ "servicesValue": "{total} در مجموع · {exposed} در معرض",
+ "buildLocation": "محل ساخت",
+ "framework": "فریمورک",
+ "app": "اپ",
+ "install": "نصب",
+ "build": "ساخت",
+ "output": "خروجی",
+ "stack": "استک",
+ "runtime": "زمان اجرا",
+ "port": "پورت",
+ "localMachine": "دستگاه محلی",
+ "cloud": "Openship Cloud",
+ "server": "سرور"
+ },
+ "checklist": {
+ "title": "چکلیست استقرار",
+ "servicesDetected": "سرویسهای شناساییشده",
+ "servicesCount": "{count} سرویس",
+ "publicDomains": "دامنههای عمومی",
+ "exposedOf": "{exposed} از {exposable} در معرض",
+ "canBeExposed": "{count} قابل انتشار",
+ "buildServices": "سرویسهای ساخت",
+ "toBuild": "{count} برای ساخت",
+ "environment": "محیط",
+ "varsAcross": "{vars} متغیر در {services} سرویس",
+ "noEnvVars": "متغیر محیطی تنظیم نشده",
+ "domains": "دامنهها"
+ },
+ "sidebar": {
+ "copyCloneToken": "کپی توکن کلون",
+ "cloneTokenTitle": "توکن کلون",
+ "cloneTokenNoRepo": "مخزن GitHub برای این استقرار وجود ندارد.",
+ "cloneTokenCopiedTitle": "توکن کلون کپی شد",
+ "cloneTokenCopied": "دستور git clone کپی شد. توکن جاسازیشده ظرف یک ساعت منقضی میشود.",
+ "cloneTokenFailed": "صدور توکن کلون ممکن نشد",
+ "newEnvironment": "محیط جدید",
+ "selectBranch": "انتخاب شاخه",
+ "saving": "در حال ذخیره…",
+ "saveChanges": "ذخیرهٔ تغییرات",
+ "deploying": "در حال استقرار…",
+ "deploy": "استقرار",
+ "freeDomainFeature": "استفاده از دامنههای رایگان .{domain} روی سرور خودتان",
+ "freeDomainDesc": "دامنههای رایگان .{domain} از طریق Openship Cloud مسیریابی میشوند. برای استقرار این پروژه روی سرور خودتان، یا Openship Cloud را وصل کنید یا این پروژه را به دامنهٔ اختصاصی تغییر دهید.",
+ "freeDomainHint": "اگر نمیخواهید Openship Cloud را وصل کنید، قبل از استقرار دامنهٔ پروژه را از رایگان به اختصاصی تغییر دهید.",
+ "servicesFreeDomainFeature": "استفاده از دامنههای رایگان .{domain} برای سرویسهایتان",
+ "servicesFreeDomainDesc": "یک یا چند سرویس منتشرشده از دامنههای رایگان .{domain} استفاده میکنند. برای استقرار آنها روی سرور خودتان، یا Openship Cloud را وصل کنید یا آن سرویسها را به دامنهٔ اختصاصی تغییر دهید.",
+ "servicesFreeDomainHint": "دامنههای اختصاصی بدون Openship Cloud کار میکنند. دامنههای مدیریتشدهٔ رایگان کار نمیکنند.",
+ "unreachableTitle": "هیچ سرویس عمومی در دسترس نیست",
+ "unreachableBody": "این استک سرویسهای اپ با پورت دارد، اما هیچکدام دامنهٔ عمومی ندارند. اگر الان استقرار کنید، سرویسها داخلی اجرا میشوند اما کاربران از URL به آنها دسترسی نخواهند داشت.",
+ "beforeDeploying": "قبل از استقرار",
+ "unreachableStep1": "سرویسی را که باید عمومی باشد انتخاب کنید.",
+ "unreachableStep2": "انتشار را برای آن سرویس فعال کنید.",
+ "unreachableStep3": "زیردامنه یا دامنهٔ اختصاصی بگذارید و پورت عمومی را انتخاب کنید.",
+ "reviewServices": "بازبینی سرویسها",
+ "deployAnyway": "بههرحال استقرار کن"
+ },
+ "skeleton": {
+ "settings1": "بارگذاری تنظیمات ذخیرهشده",
+ "settings2": "بازیابی پیکربندی",
+ "local1": "اسکن پروژه",
+ "local2": "خواندن مانیفستها",
+ "local3": "تشخیص فریمورک",
+ "repo1": "اتصال به GitHub",
+ "repo2": "دریافت مخزن",
+ "repo3": "تحلیل ساختار"
+ },
+ "targetStep": {
+ "heading": "کجا میخواهید استقرار کنید؟",
+ "loadingSubtitle": "بارگذاری مقصدهای استقرار در دسترس",
+ "loadingCheck": "در حال بررسی سرورها و اتصال ابری...",
+ "deployAndBuildHeading": "استقرار و ساخت",
+ "chooseSubtitle": "انتخاب کنید برنامه کجا اجرا شود",
+ "onlyOneSubtitle": "فعلاً فقط یک مقصد استقرار در دسترس است",
+ "noTargetSubtitle": "هنوز مقصد استقراری در دسترس نیست",
+ "noTargetBody": "Openship Cloud را وصل کنید یا سروری اضافه کنید تا این استقرار ادامه یابد.",
+ "addServer": "افزودن سرور خودتان",
+ "continue": "ادامه",
+ "chooseServer": "انتخاب سرور",
+ "thisServerBadge": "این سرور",
+ "thisServerHost": "میزبان فعلی — استقرارها اینجا اجرا میشوند",
+ "searchPlaceholder": "جستجوی سرورها…",
+ "noServersMatch": "هیچ سروری با جستجوی شما همخوان نیست.",
+ "requireCloudFeature": "استقرار روی Openship Cloud",
+ "saveDefault": "بهعنوان پیشفرض من برای هر استقرار ذخیره شود.",
+ "saveDefaultHint": "بعداً در تنظیمات عوضش کنید، یا برای هر استقرار از انتخابگر همین صفحه.",
+ "savedToastTitle": "پیشفرضها",
+ "savedToast": "بهعنوان مقصد پیشفرض استقرار ذخیره شد",
+ "saveFailedToast": "ذخیرهٔ پیشفرض ممکن نشد — استقرار ادامه مییابد",
+ "gitForwardLabel": "اعتبارنامهٔ git من را فوروارد کن",
+ "gitForwardDescPre": "کلون روی سرور با هویت محلی ",
+ "gitForwardDescPost": " — بدون ساخت و آپلود. چیزی روی سرور ذخیره نمیشود.",
+ "options": {
+ "serverViaSsh": "استقرار روی سرور راه دور از طریق SSH.",
+ "servers": "سرورهای شما",
+ "serversCount": "{count} سرور پیکربندیشده.",
+ "cloud": "Openship Cloud",
+ "cloudConnectedDesc": "استقرار روی زیرساخت ابری مدیریتشده. نیازی به راهاندازی سرور نیست.",
+ "cloudDisconnectedDesc": "حساب Openship Cloud را وصل کنید و روی زیرساخت مدیریتشده استقرار کنید."
+ },
+ "build": {
+ "heading": "محل ساخت",
+ "prepareHeading": "محل آمادهسازی",
+ "subtitle": "ساخت کجا اجرا شود",
+ "prepareSubtitle": "مخزن کجا مرحلهبندی شود",
+ "advanced": "پیشرفته",
+ "advancedSummary": "{action} روی {location}",
+ "actionBuild": "ساخت",
+ "actionPrepare": "آمادهسازی",
+ "options": "گزینهها",
+ "localLabel": "همین دستگاه",
+ "localDesc": "اینجا بساز، خروجی را بفرست.",
+ "remoteLabel": "راه دور",
+ "remoteDesc": "روی مقصد استقرار بساز.",
+ "cloudLabel": "Openship Cloud",
+ "cloudDesc": "ساخت ابری مدیریتشده.",
+ "cloudLocalLabel": "همین دستگاه",
+ "cloudLocalDesc": "اینجا بساز، خروجی را آپلود کن."
+ },
+ "hint": {
+ "buildTitle": "همانجا که استقرار میکنید میسازیم",
+ "prepareTitle": "همانجا که استقرار میکنید آماده میکنیم",
+ "cloud": "ساخت در زیرساخت ابری مدیریتشده اجرا میشود — چیزی برای راهاندازی نیست.",
+ "serverNamed": "ساخت روی سرور شما «{name}» اجرا میشود. اگر VPS کوچکی است، ساخت روی همین دستگاه خیلی سریعتر است. در صفحهٔ بعد عوضش کنید یا ترجیح را در تنظیمات ذخیره کنید.",
+ "server": "ساخت روی سرور شما اجرا میشود. اگر VPS کوچکی است، ساخت روی همین دستگاه خیلی سریعتر است. در صفحهٔ بعد عوضش کنید یا ترجیح را در تنظیمات ذخیره کنید.",
+ "local": "ساخت روی همین دستگاه اجرا میشود. در صفحهٔ بعد عوضش کنید یا ترجیح را در تنظیمات ذخیره کنید.",
+ "chooseBuildLocation": "انتخاب محل ساخت"
+ },
+ "clone": {
+ "heading": "محل کلون",
+ "descLead": "ساخت همیشه روی سرور اجرا میشود. ",
+ "descDesktop": "اعتبارنامه موقتاً فوروارد میشود، هرگز ذخیره نمیشود.",
+ "descServer": "از توکن کوتاهعمر استفاده میکند.",
+ "apiHostDesktopLabel": "روی دستگاه شما",
+ "apiHostServerLabel": "روی Openship",
+ "apiHostDesktopDesc": "اینجا کلون کن، سپس آپلود کن.",
+ "apiHostServerDesc": "روی Openship کلون کن، سپس آپلود کن.",
+ "serverLabel": "روی سرور",
+ "serverDesc": "روی سرور کلون کن. بدون آپلود."
+ },
+ "rollbackTitle": "بازگردانی و پشتیبانها",
+ "rollbackHint": "چند نسخهٔ قبلی روی این سرور قابل بازگردانی بماند، و قبل از استقرار از چه چیزی پشتیبان گرفته شود.",
+ "rollbackFirstDeploy": "پس از اولین استقرار پروژه، در تنظیمات پروژه قابل ویرایش است.",
+ "backupSummaryNone": "هنوز سیاست پشتیبانی نیست",
+ "backupSummaryConfigured": "{count} سیاست پشتیبان · پیش از استقرار: {preDeploy}",
+ "backupManage": "مدیریت پشتیبانها",
+ "on": "روشن",
+ "off": "خاموش"
+ },
+ "summary": {
+ "buildAndDeploy": "ساخت و استقرار:",
+ "build": "ساخت:",
+ "deploy": "استقرار:",
+ "static": "استاتیک",
+ "runtimeStatic": "استاتیک · سرو از لبه",
+ "runtimeSandboxed": "سندباکسشده",
+ "runtimeDirectWarning": "بدون سندباکس",
+ "runtimeDirectHint": "مستقیم روی میزبان اجرا میشود — بدون سندباکس.",
+ "targetLocal": "همین دستگاه",
+ "targetServer": "سرور من",
+ "targetCloud": "Openship Cloud",
+ "buildLocal": "همین دستگاه",
+ "buildRemote": "راه دور",
+ "rollbackOne": "{count} نسخه",
+ "rollbackOther": "{count} نسخه",
+ "rollbackAuto": "بازگردانی: خودکار",
+ "rollbackSnapshotHint": "از هر نسخه یک کپی نگه داشته میشود تا بازگردانی دوباره ساخته نشود. در پیشرفته تغییر دهید.",
+ "rollbackGitHint": "چند نسخهٔ قبلی قابل بازگردانی بماند. در پیشرفته تغییر دهید."
+ },
+ "power": {
+ "heading": "توان",
+ "subtitle": "اندازهٔ زمان اجرا را برای Openship Cloud انتخاب کنید",
+ "ram": "RAM",
+ "disk": "دیسک",
+ "vcpu": "vCPU",
+ "custom": "سفارشی",
+ "customDesc": "CPU، RAM و فضای ذخیرهسازی را خودتان تنظیم کنید",
+ "tierMicroLabel": "Micro",
+ "tierMicroBestFor": "پروژههای جانبی، ترافیک کم",
+ "tierLowLabel": "Low",
+ "tierLowBestFor": "اپهای کوچک، APIهای سبک",
+ "tierMediumLabel": "Medium",
+ "tierMediumBestFor": "بیشتر اپهای تولیدی",
+ "tierHighLabel": "High",
+ "tierHighBestFor": "بارهای سنگین، ساخت سریع",
+ "modalTitle": "زمان اجرای سفارشی",
+ "modalSubtitle": "CPU، RAM و دیسک این استقرار را انتخاب کنید.",
+ "vcpuField": "vCPU",
+ "ramField": "RAM (MB)",
+ "diskField": "دیسک (GB)",
+ "cancel": "لغو",
+ "save": "ذخیره"
+ },
+ "runtime": {
+ "heading": "زمان اجرا",
+ "subtitle": "چطور ایزوله میشود",
+ "subtitleRam": "چطور ایزوله میشود · {ram} GB RAM",
+ "recommended": "پیشنهادی",
+ "sandboxedLabel": "سندباکسشده",
+ "sandboxedDesc": "کانتینر ایزوله. سربار ناچیز.",
+ "directLabel": "مستقیم",
+ "directDesc": "روی میزبان اجرا میشود. کمترین سربار.",
+ "caveatLowRam": "RAM اینجا محدود است — سندباکس حدود ۵۰–۸۰ MB اضافه میکند. مستقیم سبکتر است، سندباکس امنتر.",
+ "caveat": "مستقیم یعنی در صورت نفوذ، دسترسی کامل به میزبان. سندباکس حدود ۱–۲٪ CPU برای ایزولاسیون واقعی اضافه میکند."
+ },
+ "serveMode": {
+ "heading": "چطور سرو میشود",
+ "subtitle": "فایلهای ساختهشده را سرو کنید، یا بهصورت فرایند سرور زنده اجرا کنید.",
+ "staticLabel": "سرو بهصورت فایل استاتیک",
+ "staticDesc": "خروجی ساخت مستقیماً از لبه سرو میشود. هیچ فرایندی اجرا نمیشود.",
+ "serverLabel": "اجرا بهصورت فرایند سرور",
+ "serverDesc": "یک فرایند طولانیمدت راهاندازی کنید (نیاز به دستور شروع و پورت دارد).",
+ "staticNote": "در سندباکس ایزولهٔ Docker ساخته میشود، سپس بهصورت فایل استاتیک از لبه سرو میشود.",
+ "startCommandLabel": "دستور شروع",
+ "startCommandPlaceholder": "npm start",
+ "startCommandHint": "خالی بگذارید تا خروجی ساخت بهصورت فایل استاتیک از لبه سرو شود (چیزی اجرا نمیشود). دستوری وارد کنید تا بهصورت فرایند سرور زنده اجرا شود.",
+ "modeStatic": "استاتیک",
+ "modeProcess": "فرایند سرور",
+ "portLabel": "پورت"
+ },
+ "mail": {
+ "title": "استقرار وبمیل",
+ "subtitle": "یک وبمیل خودمیزبان روی دامنهای که انتخاب میکنید برپا کنید.",
+ "backToMail": "بازگشت به ایمیل",
+ "missingServerTitle": "سرور ایمیل موجود نیست",
+ "missingServerBody": "این صفحه را از نمای کلی ایمیل باز کنید.",
+ "deployToTitle": "استقرار روی",
+ "deployToHint": "وبمیل میتواند روی همین سرور ایمیل، روی میزبان مدیریتشدهٔ دیگری از openship، یا روی Opshcloud باشد.",
+ "loadingTargets": "در حال بارگذاری مقصدها…",
+ "notAvailable": "در دسترس نیست",
+ "domainTitle": "دامنه",
+ "domainHintProxy": "سرور ایمیل شما از قبل مالک این hostname است — آن را از طریق VPS ایمیل به بار کاری Opshcloud پروکسی میکنیم. تغییر DNS لازم نیست.",
+ "domainHintMailHost": "hostname خود سرور ایمیل شما — از قبل به اینجا اشاره دارد و گواهی دارد، پس چیزی برای راهاندازی نیست. اگر ترجیح میدهید جدا بمانند از webmail.<دامنهٔ شما> استفاده کنید.",
+ "domainHintCloud": "یک CNAME به URL از نوع *.opsh.io که فراهم میکنیم اشاره دهید (پس از استقرار میبینید).",
+ "domainHintDefault": "URLای که اپراتورها باز میکنند. DNS باید به مقصد استقرار اشاره کند.",
+ "summary": "خلاصه",
+ "summaryTarget": "مقصد",
+ "summaryDomain": "دامنه",
+ "summaryMailServer": "سرور ایمیل",
+ "starting": "در حال شروع…",
+ "deployButton": "استقرار وبمیل",
+ "replaceButton": "جایگزینی وبمیل",
+ "legacyTitle": "این وبمیل قدیمیتر از اپ وبمیل است",
+ "legacyBody": "با Openship قدیمیتری مستقر شده که وبمیل را بهصورت باندل میفرستاد نه ایمیج، بنابراین نمیتوان دوباره مستقرش کرد. استقرار الان آن را حذف میکند — پروژه، کانتینر و پایگاهدادهٔ نشست — و اپ وبمیل را جایش نصب میکند. همه از آن خارج میشوند و تنظیمات وبمیل هر کاربر از بین میرود.",
+ "legacyKept": "صندوقها، دادهٔ ایمیل و همهٔ پیامها روی سرور ایمیل دستنخورده میمانند.",
+ "deployFailedTitle": "استقرار ناموفق بود",
+ "deployFailed": "شروع استقرار ناموفق بود"
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/fa/deployments.json b/apps/dashboard/src/i18n/locales/fa/deployments.json
new file mode 100644
index 000000000..9930fc07b
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/fa/deployments.json
@@ -0,0 +1,194 @@
+{
+ "time": {
+ "secondsAgo": "{n} ثانیه پیش",
+ "minutesAgo": "{n} دقیقه پیش",
+ "hoursAgo": "{n} ساعت پیش",
+ "daysAgo": "{n} روز پیش",
+ "monthsAgo": "{n} ماه پیش",
+ "yearsAgo": "{n} سال پیش"
+ },
+ "header": {
+ "title": "استقرارها",
+ "loading": "در حال بارگذاری…",
+ "countProjectOne": "{count} استقرار",
+ "countProjectOther": "{count} استقرار",
+ "countAllOne": "{deployments} در مجموع در {projects} پروژه",
+ "countAllOther": "{deployments} در مجموع در {projects} پروژه"
+ },
+ "filters": {
+ "searchPlaceholder": "جستجوی استقرارها...",
+ "all": "همه",
+ "success": "موفق",
+ "failed": "ناموفق",
+ "building": "در حال ساخت",
+ "canceled": "لغو شده"
+ },
+ "projectFilter": {
+ "allProjects": "همهٔ پروژهها"
+ },
+ "sidebar": {
+ "overview": {
+ "title": "نمای کلی",
+ "total": "مجموع",
+ "successful": "موفق",
+ "failed": "ناموفق",
+ "inProgress": "در حال انجام"
+ },
+ "autoDeploy": {
+ "title": "استقرار خودکار",
+ "description": "استقرار خودکار را فعال کنید تا با هر پوش به شاخهٔ اصلی، استقرار بهطور خودکار شروع شود.",
+ "cta": "رفتن به تنظیمات پروژه"
+ },
+ "getStarted": {
+ "title": "شروع کنید",
+ "description": "یک مخزن Git وارد کنید یا از قالب برای ساخت اولین استقرار استفاده کنید.",
+ "cta": "استقرار یک پروژه"
+ },
+ "recent": {
+ "title": "اخیر",
+ "empty": "استقرارهای شما اینجا ظاهر میشوند",
+ "unknown": "نامشخص"
+ }
+ },
+ "stats": {
+ "total": "مجموع",
+ "projectCountOne": "{count} پروژه",
+ "projectCountOther": "{count} پروژه",
+ "success": "موفق",
+ "failed": "ناموفق",
+ "inProgress": "در حال انجام",
+ "buildingNow": "الان در حال ساخت"
+ },
+ "status": {
+ "deployed": "مستقر شده",
+ "failed": "ناموفق",
+ "canceled": "لغو شده",
+ "building": "در حال ساخت",
+ "deploying": "در حال استقرار",
+ "partial": "جزئی",
+ "actionRequired": "اقدام لازم است",
+ "rejected": "رد شده",
+ "verifying": "در حال تأیید",
+ "noChanges": "بدون تغییر",
+ "pending": "در انتظار"
+ },
+ "serviceStatus": {
+ "deployed": "مستقر شده",
+ "failed": "ناموفق",
+ "cancelled": "لغو شده",
+ "skipped": "رد شده",
+ "building": "در حال ساخت",
+ "deploying": "در حال استقرار",
+ "running": "در حال اجرا",
+ "removedOnHost": "از میزبان حذف شد",
+ "verifying": "در حال تأیید",
+ "pending": "در انتظار"
+ },
+ "card": {
+ "unknownProject": "پروژهٔ نامشخص",
+ "versionTitle": "نسخهٔ {version}",
+ "active": "فعال",
+ "activeTitle": "این استقراری است که الان پروژه را سرو میکند",
+ "pinned": "سنجاقشده",
+ "pinnedTitle": "سنجاقشده — از هرس نگهداری معاف است. برای همیشه قابل بازگردانی میماند.",
+ "snapshotted": "اسنپشات شده",
+ "snapshottedTitle": "آرتیفکت بایگانی شد — این نسخه برای بازگردانی در دسترس است",
+ "manualDeploy": "استقرار دستی",
+ "serviceTitle": "{name}: {label}",
+ "serviceTitleReason": "{name}: {label} ({reason})"
+ },
+ "empty": {
+ "filtered": {
+ "title": "استقراری پیدا نشد",
+ "description": "فیلترها را عوض کنید یا جستجو را پاک کنید."
+ },
+ "none": {
+ "title": "هنوز استقراری نیست",
+ "description": "اولین پروژه را مستقر کنید تا اینجا با وضعیت ساخت، جزئیات کامیت و معیارهای عملکرد ظاهر شود.",
+ "deployButton": "استقرار پروژه",
+ "browseButton": "مرور قالبها"
+ }
+ },
+ "modal": {
+ "title": "جزئیات کامیت",
+ "project": "پروژه",
+ "unknownProject": "پروژهٔ نامشخص",
+ "domain": "دامنه",
+ "noDomain": "بدون دامنه",
+ "status": "وضعیت",
+ "environment": "محیط",
+ "statusName": {
+ "success": "موفق",
+ "failed": "ناموفق",
+ "building": "در حال ساخت",
+ "deploying": "در حال استقرار",
+ "canceled": "لغو شده",
+ "pending": "در انتظار"
+ },
+ "changedFiles": "فایلهای تغییر یافته ({count})",
+ "fileTypes": {
+ "added": "+ افزودهشده",
+ "modified": "~ ویرایششده",
+ "removed": "- حذفشده"
+ },
+ "viewOnGithub": "مشاهده در GitHub",
+ "visitSite": "بازدید از سایت",
+ "manual": {
+ "title": "استقرار دستی",
+ "description": "این استقرار بهصورت دستی شروع شده و اطلاعات کامیت ندارد.",
+ "deploymentId": "شناسهٔ استقرار",
+ "created": "ایجاد شده",
+ "type": "نوع",
+ "typeManual": "دستی"
+ }
+ },
+ "menu": {
+ "openDeployment": "باز کردن استقرار",
+ "viewRepository": "مشاهدهٔ مخزن",
+ "copyDomainUrl": "کپی URL دامنه",
+ "copyBuildId": "کپی شناسهٔ ساخت",
+ "cancelDeployment": "لغو استقرار",
+ "rollback": "بازگردانی به این نسخه",
+ "rollbackTitle": {
+ "enabled": "پروژه را به این نسخه برگردانید (فقط کد — داده تغییر نمیکند)",
+ "active": "این از قبل نسخهٔ فعال است",
+ "pruned": "این استقرار ایمیج نگهداریشده و کامیتی برای ساخت دوباره ندارد",
+ "notReady": "فقط استقرارهای آماده قابل بازگردانی هستند",
+ "rebuildOnly": "این نسخه را با ساخت دوبارهٔ کامیتش برگردانید (ایمیجش هرس شده)"
+ },
+ "pin": "سنجاق",
+ "unpin": "برداشتن سنجاق",
+ "pinTitle": {
+ "pin": "این استقرار را برای همیشه قابل بازگردانی نگه دارید (سقف: ۱۰ سنجاق در هر پروژه)",
+ "unpin": "اجازه دهید وقتی ظرفیت نگهداری پر شد این استقرار هرس شود",
+ "pruned": "نمیتوان سنجاق کرد — آرتیفکت از قبل هرس شده"
+ },
+ "deleteDeployment": "حذف استقرار",
+ "rollbackFailed": "بازگردانی ناموفق بود",
+ "pinFailed": "سنجاق کردن استقرار ناموفق بود",
+ "unpinFailed": "برداشتن سنجاق ناموفق بود",
+ "rollbackModeInstant": "بازگردانی فوری — ایمیج نگهداریشدهٔ این نسخه را دوباره استفاده میکند، بدون ساخت دوباره.",
+ "rollbackModeRebuild": "ساخت دوباره — ایمیج این نسخه دیگر روی میزبان نیست، پس کامیتش دوباره ساخته میشود.",
+ "rollbackModeMixed": "عمدتاً فوری — {count} سرویس ایمیجشان را از دست دادهاند و دوباره ساخته میشوند."
+ },
+ "rollbackConfirm": {
+ "title": "به این نسخه بازگردانی شود؟",
+ "dataUnchanged": "این فقط کد برنامه را برمیگرداند. دادهٔ پایگاهداده و والیوم تغییر نمیکند.",
+ "envHeading": "متغیرهای محیطی",
+ "envSummaryOverlay": "{count} متغیر تغییر میکند — مقادیر این انتشار روی مقادیر فعلی بازنشانی میشود.",
+ "envSummaryReplace": "{count} متغیر تغییر میکند — محیط این انتشار دقیقاً همانطور که ثبت شده استفاده میشود.",
+ "envNoChanges": "هیچ متغیر محیطی تغییر نمیکند.",
+ "envUnchanged": "کانتینر موجود بهجای بازسازی، ریاستارت میشود؛ بنابراین محیطش دستنخورده میماند.",
+ "envNoValues": "مقادیر اینجا هرگز نشان داده نمیشوند.",
+ "envTruncated": "نمایش {shown} از {total}.",
+ "dirFrozenWins": "به مقدار این انتشار برمیگردد",
+ "dirRemovedSince": "برمیگردد — از زمان این انتشار حذف شده بود",
+ "dirAddedSinceKept": "همانطور که الان هست نگه داشته میشود — از زمان این انتشار اضافه شده",
+ "dirAddedSinceDropped": "حذف میشود — از زمان این انتشار اضافه شده",
+ "scopeAmbiguous": "بدون محدودهٔ سرویس ثبت شده، بنابراین یک مقدار روی همهٔ سرویسها مینشیند",
+ "untouchedHeading": "سرویسهای اضافهشده از زمان این انتشار",
+ "untouchedNote": "اینها به اجرا ادامه میدهند و دست نخورده میمانند: {names}",
+ "confirm": "بازگردانی",
+ "cancel": "لغو"
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/fa/emails.json b/apps/dashboard/src/i18n/locales/fa/emails.json
new file mode 100644
index 000000000..a0b805ff1
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/fa/emails.json
@@ -0,0 +1,200 @@
+{
+ "page": {
+ "title": "سرور ایمیل",
+ "subtitle": "سرور ایمیل خودمیزبان را با چند کلیک راهاندازی کنید",
+ "backToServers": "بازگشت به سرورهای ایمیل",
+ "backToAdmin": "بازگشت به مدیریت ایمیل",
+ "addServer": "افزودن سرور ایمیل",
+ "removeModal": {
+ "title": "این سرور ایمیل حذف شود؟",
+ "description": "{label} فقط از فهرست ایمیل برداشته میشود. استک ایمیل به اجرا ادامه میدهد و چیزی حذف نصب نمیشود — هر وقت بخواهید با «پذیرش» برای نصب موجود دوباره اضافهاش کنید.",
+ "submit": "حذف از فهرست",
+ "submitting": "در حال حذف…",
+ "body": "چون نصب روی سرور دستنخورده میماند، پذیرش دوباره بعداً مدیریت کامل را بدون نصب مجدد برمیگرداند."
+ },
+ "errors": {
+ "resetFailed": "بازنشانی ناموفق بود",
+ "ackDns": "تأیید DNS ممکن نشد",
+ "ackPtr": "تأیید PTR ممکن نشد",
+ "selectServer": "اول یک سرور انتخاب کنید",
+ "resolutionFailed": "حل ناموفق بود"
+ }
+ },
+ "adopt": {
+ "title": "پذیرش سرور ایمیل موجود",
+ "intro": "از قبل با Openship روی یکی از سرورها سرور ایمیل راهاندازی کردهاید؟ آن را انتخاب و اسکن کنید — اگر استک ایمیل پیدا شود، بدون نصب مجدد و بدون دست زدن به دادهها دوباره به داشبورد پذیرفته میشود.",
+ "scanning": "در حال اسکن…",
+ "scan": "اسکن سرور",
+ "detected": "سرور ایمیل شناسایی شد",
+ "domainLabel": "دامنه:",
+ "unknown": "نامشخص",
+ "stats": "iRedMail در حال اجرا: {iredmail} · نصب: {install} · وبمیل: {webmail}",
+ "yes": "بله",
+ "no": "خیر",
+ "complete": "کامل",
+ "inProgress": "در حال انجام",
+ "present": "موجود",
+ "notDeployed": "مستقر نشده",
+ "cancel": "لغو",
+ "adopt": "پذیرش",
+ "noInstall": "نصب سرور ایمیل روی این سرور پیدا نشد.",
+ "scanFailed": "اسکن ناموفق بود — سرور در دسترس است؟",
+ "adoptFailed": "پذیرش ناموفق بود"
+ },
+ "dns": {
+ "heading": "قبل از ادامه، رکوردهای DNS را منتشر کنید",
+ "bodyBefore": "بدون اینها تحویل ایمیل قطع میشود. آنها را در ارائهدهندهٔ DNS اضافه کنید، سپس بزنید ",
+ "action": "رکوردها را گذاشتم — ادامه",
+ "bodyAfterResume": " در پایین. نصب از گام {resumeStep} (گواهی SSL) ادامه مییابد.",
+ "bodyAfter": " در پایین.",
+ "autoConfigure": "پیکربندی خودکار DNS",
+ "autoConfigureDesc": "ارائهدهندهای را که DNS دامنه را میزبانی میکند انتخاب کنید. رکوردها را با API آنها برایتان میفرستیم.",
+ "otherManual": "سایر / تنظیم دستی",
+ "otherManualDesc": "هر رکورد را از بنر کپی کنید و در رابط ارائهدهنده بچسبانید",
+ "pickAnother": "انتخاب ارائهدهندهٔ دیگر",
+ "comingSoon": "یکپارچگی {provider} — بهزودی",
+ "comingSoonBefore": "یکپارچگی مستقیم API با این ارائهدهنده هنوز آماده نیست. فعلاً هر رکورد را از بنر کپی کنید و در رابط ارائهدهندهٔ DNS بچسبانید. وقتی تمام شد، بزنید ",
+ "comingSoonAfter": ".",
+ "gotIt": "فهمیدم",
+ "providers": {
+ "cloudflare": "DNS رایگان، انتشار سریع. رایجترین انتخاب.",
+ "hostinger": "اگر دامنه را از Hostinger خریده باشید.",
+ "route53": "زونهای DNS میزبانیشده در AWS.",
+ "digitalocean": "زونهای مدیریتشدهٔ DigitalOcean.",
+ "namecheap": "DNS پیشفرض Namecheap.",
+ "google": "زونهای مدیریتشدهٔ Google."
+ }
+ },
+ "recordCard": {
+ "priority": "اولویت: {priority}",
+ "copyValue": "کپی مقدار"
+ },
+ "progress": {
+ "liveLogs": "لاگ زنده",
+ "lineOne": "{count} خط",
+ "lineOther": "{count} خط",
+ "cancel": "لغو",
+ "resetTitle": "وضعیت نصب روی سرور پاک شود و از نو شروع شود",
+ "confirmReset": "برای تأیید دوباره کلیک کنید",
+ "reset": "بازنشانی",
+ "waiting": "در انتظار خروجی…",
+ "noBuffered": "خروجی بافری برای این اجرا نیست.",
+ "willStream": "وقتی راهاندازی شروع شود لاگها اینجا استریم میشوند.",
+ "jumpToLatest": "پرش به آخرین",
+ "setupFailed": "راهاندازی ناموفق بود",
+ "retryFromStep": "تلاش دوباره از گام {resumeStep}",
+ "resumePausedTitle": "راهاندازی متوقف شد",
+ "stepLabel": "گام {step}",
+ "continueFromStep": "ادامه از گام {step}"
+ },
+ "serverList": {
+ "title": "سرورهای ایمیل",
+ "addServer": "افزودن سرور ایمیل",
+ "removeFromList": "حذف از فهرست",
+ "removeAria": "حذف {name} از فهرست ایمیل",
+ "installing": "در حال نصب",
+ "running": "در حال اجرا",
+ "incomplete": "ناتمام",
+ "stoppedAt": "متوقف · گام {step}",
+ "open": "باز کردن",
+ "openAria": "باز کردن {name}",
+ "actionsAria": "اقدامات برای {name}",
+ "overviewTitle": "نمای کلی ایمیل",
+ "hint": "حذف سرور فقط آن را از این فهرست جدا میکند — نصب به اجرا ادامه میدهد و میتوان دوباره پذیرفتش."
+ },
+ "setup": {
+ "title": "راهاندازی سرور ایمیل",
+ "subtitle": "یک استک کامل ایمیل خودمیزبان روی این سرور مستقر کنید",
+ "domainLabel": "دامنه",
+ "domainPlaceholder": "example.com",
+ "willBeAtBefore": "سرور ایمیل شما در این آدرس خواهد بود ",
+ "adminPasswordLabel": "رمز عبور مدیر",
+ "passwordPlaceholder": "رمز قوی — یا تولید را بزنید",
+ "passwordHint": "حداقل ۱۲ نویسه. برای ورود به صندوق پس از راهاندازی استفاده میشود — بعداً از پنل مدیریت پایین عوضش کنید.",
+ "relay": {
+ "toggle": "خروجی را از طریق ارائهدهندهٔ ایمیل رله کن",
+ "hint": "پیشنهادی. از IPهای معتبر ارائهدهنده بفرستید — IP سرور تازه اغلب در بلاکلیست است و خیلی از میزبانها پورت ۲۵ خروجی را میبندند. دریافت روی همین سرور میماند.",
+ "region": "منطقه",
+ "username": "نام کاربری SMTP",
+ "password": "رمز عبور SMTP",
+ "perDomainNote": "برای همهٔ دامنهها اعمال میشود. مسیریابی هر دامنه و رکوردهای DKIM / MAIL FROM ارائهدهنده پس از نصب در زبانهٔ ارسال پیکربندی میشود."
+ },
+ "startSetup": "شروع راهاندازی",
+ "startFailed": "راهاندازی شروع نشد",
+ "startFailedFallback": "راهاندازی شروع نشد و سرور دلیلی نداد. لاگهای API را بررسی کنید.",
+ "adoptCta": "از قبل سرور ایمیل دارید؟ بپذیریدش",
+ "whatInstalled": "چه چیزهایی نصب میشود",
+ "features": {
+ "stackLabel": "استک سرور ایمیل",
+ "stackDesc": "Postfix، Dovecot، ضد هرزنامه",
+ "sslLabel": "گواهی SSL",
+ "sslDesc": "SSL خودکار Let's Encrypt",
+ "dnsLabel": "پیکربندی DNS",
+ "dnsDesc": "رکوردهای DKIM، SPF، DMARC",
+ "adminLabel": "پنل مدیریت",
+ "adminDesc": "مدیریت صندوقها، دامنهها و نامهای مستعار"
+ },
+ "prerequisites": "پیشنیازها",
+ "prereq1": "دامنهای با دسترسی DNS",
+ "prereq2": "پورت ۲۵ توسط ارائهدهنده مسدود نباشد",
+ "prereq2Relay": "پورت ۲۵ برای ایمیل ورودی باز باشد (خروجی از رله میرود)",
+ "prereq3": "سرور تمیز Ubuntu/Debian (پیشنهادی)",
+ "hide": "پنهان",
+ "reveal": "نمایش",
+ "generateTitle": "تولید یک رمز تصادفی قوی",
+ "generate": "تولید"
+ },
+ "sidebar": {
+ "portConflict": {
+ "title": "تداخل پورت شناسایی شد",
+ "subtitle": "پورتهای ۸۰/۴۴۳ برای سرور ایمیل لازم است",
+ "managed": "مدیریتشده",
+ "known": "سرویس شناختهشده",
+ "unknown": "نامشخص",
+ "container": "کانتینر: {name}",
+ "killWarning": "قبل از کشتن این فرایند مطمئن شوید لازم نیست. این کار برگشتناپذیر است.",
+ "allResolved": "همهٔ تداخلها حل شد",
+ "rerun": "بررسی پورت را دوباره اجرا کنید و راهاندازی را ادامه دهید.",
+ "resumeStep3": "ادامه از گام ۳"
+ },
+ "completion": {
+ "ready": "سرور ایمیل آماده است!",
+ "webmail": "وبمیل",
+ "adminPanel": "پنل مدیریت"
+ },
+ "dnsRef": {
+ "title": "رکوردهای DNS لازم",
+ "subtitle": "مرجع — برای دیدن رکوردهای منتشرشده ضربه بزنید"
+ },
+ "steps": {
+ "title": "همهٔ گامها",
+ "interrupted": "نصب قبل از گام {step} قطع شد. ادامه دهید تا از همانجا پی گرفته شود.",
+ "resume": "ادامه",
+ "retry": "تلاش دوباره"
+ },
+ "details": {
+ "title": "جزئیات راهاندازی",
+ "domain": "دامنه",
+ "mailServer": "سرور ایمیل",
+ "started": "شروع شده"
+ }
+ },
+ "ptr": {
+ "heading": "قبل از ادامه، DNS معکوس (PTR) را تنظیم کنید",
+ "bodyBefore": "PTRها در ",
+ "bodyStrongPanel": "پنل ارائهدهندهٔ VPS",
+ "bodyMid": " پیکربندی میشوند (Hostinger، DigitalOcean، AWS و غیره)، ",
+ "bodyStrongNot": "نه",
+ "bodyAfter": " در ارائهدهندهٔ DNS. بدون rDNS منطبق، Gmail و Outlook ایمیل خروجی شما را رد میکنند. نصب از گام {resumeStep} (SSL) ادامه مییابد.",
+ "recommended": "پیشنهادی",
+ "ipLabel": "IP",
+ "setTo": "تنظیم به",
+ "copy": "کپی",
+ "howTo": "چطور تنظیمش کنید:",
+ "howToBody": " وارد پنل ارائهدهندهٔ VPS شوید، تنظیمات شبکه / DNS / rDNS سرور را پیدا کنید و رکورد PTR هر IP را روی ",
+ "howToAfter": " بگذارید. انتشار معمولاً ۵ تا ۱۵ دقیقه طول میکشد.",
+ "verifyWith": "تأیید با:",
+ "shouldReturn": " باید برگرداند ",
+ "action": "PTRها را گذاشتم — ادامه"
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/fa/emailsAdmin.json b/apps/dashboard/src/i18n/locales/fa/emailsAdmin.json
new file mode 100644
index 000000000..338d2741e
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/fa/emailsAdmin.json
@@ -0,0 +1,743 @@
+{
+ "panel": {
+ "tabs": {
+ "overview": "نمای کلی",
+ "domains": "دامنهها",
+ "mailboxes": "صندوقهای پستی",
+ "aliases": "نامهای مستعار",
+ "notifications": "اعلانها",
+ "dns": "DNS",
+ "health": "سلامت",
+ "backup": "پشتیبان",
+ "sending": "ارسال",
+ "advanced": "پیشرفته"
+ },
+ "ariaLabel": "بخشهای مدیریت ایمیل"
+ },
+ "notifications": {
+ "heading": "قوانین اعلان",
+ "description": "وقتی نامه به آدرسی روی این سرور میرسد در کانال اعلان خبردار شوید.",
+ "newRule": "قانون جدید",
+ "newTitle": "قانون اعلان جدید",
+ "editTitle": "ویرایش قانون اعلان",
+ "edit": "ویرایش",
+ "emptyTitle": "هنوز قانون اعلانی نیست",
+ "emptyBody": "قانونی اضافه کنید تا وقتی نامه به صندوق پستی یا دامنهٔ این سرور میرسد در Slack، Telegram، Discord یا وبهوک خبردار شوید.",
+ "noChannels": "هنوز کانال اعلان تأییدشدهای ندارید. یکی را در تنظیمات → اعلانها اضافه و تأیید کنید، سپس برای ساخت قانون برگردید.",
+ "addChannel": "افزودن کانال اعلان",
+ "manageChannels": "مدیریت کانالها",
+ "fieldName": "نام",
+ "namePlaceholder": "صندوق پشتیبانی → #support",
+ "fieldScope": "پایش",
+ "scopeMailbox": "یک صندوق پستی",
+ "scopeDomain": "یک دامنهٔ کامل",
+ "scopeAll": "هر دامنه روی این سرور",
+ "scopeAllSummary": "هر دامنه",
+ "fieldAddress": "آدرس",
+ "fieldDomain": "دامنه",
+ "mailboxCaveat": "از هدرهای To و Cc تطبیق داده میشود، پس نامهای که با Bcc یا از طریق نام مستعار به این صندوق رسیده تطبیق نمیخورد. اگر به هر پیام نیاز دارید کل دامنه را پایش کنید.",
+ "fieldFrom": "فرستنده شامل",
+ "fieldSubject": "موضوع شامل",
+ "patternPlaceholder": "اختیاری",
+ "patternHint": "حساس به بزرگی حروف نیست. از * بهعنوان wildcard استفاده کنید — بقیه تحتاللفظی تطبیق میخورند.",
+ "fieldChannels": "به این کانالها اطلاع بده",
+ "fieldEnabled": "قانون فعال است",
+ "channelCount": "{count} کانال",
+ "colRule": "قانون",
+ "colWatch": "در حال پایش",
+ "colChannels": "کانالها",
+ "colStatus": "وضعیت",
+ "active": "فعال",
+ "disabled": "خاموش",
+ "paused": "متوقف",
+ "deleteTitle": "حذف قانون اعلان",
+ "deleteConfirm": "حذف قانون",
+ "deleting": "در حال حذف…",
+ "deleteHint": "اعلانهای موجود تحت تأثیر نیستند. نامهٔ گرفتهشده اما هنوز پردازشنشده دور ریخته میشود.",
+ "save": "ذخیرهٔ قانون",
+ "saving": "در حال ذخیره…",
+ "cancel": "لغو",
+ "test": "آزمایش الان",
+ "testing": "در حال آزمایش…",
+ "testResult": "{read} پیام گرفتهشده خوانده شد: {matched} با قانون تطبیق خورد، {dropped} فیلتر شد. چیزی ارسال یا حذف نشد.",
+ "testFailed": "نتوانستیم آزمایش را اجرا کنیم.",
+ "loadFailed": "نتوانستیم قوانین اعلان را بارگذاری کنیم.",
+ "saveFailed": "نتوانستیم قانون را ذخیره کنیم.",
+ "deleteFailed": "نتوانستیم قانون را حذف کنیم.",
+ "confirmDelete": "قانون «{name}» حذف شود؟ گرفتن برای آدرسش متوقف میشود مگر قانون دیگری هنوز به آن نیاز داشته باشد."
+ },
+ "sending": {
+ "title": "ارسال خروجی",
+ "subtitle": "دریافت روی این سرور میماند. انتخاب کنید نامهٔ خروجی چگونه آن را ترک کند.",
+ "modeDirectTitle": "تحویل مستقیم",
+ "modeDirectDesc": "این سرور خودش نامهٔ خروجی را تحویل میدهد. ساده است، اما IPهای VPS تازه اغلب در فهرست سیاهاند و بسیاری میزبانها پورت ۲۵ خروجی را میبندند — نامه ممکن است در هرزنامه بیفتد یا شکست بخورد.",
+ "modeRelayTitle": "رله از طریق ارائهدهنده",
+ "modeRelayDesc": "نامهٔ خروجی را به یک smarthost بسپارید — SES، SendGrid، Mailgun، Postmark، Resend، یا هر سرور SMTP. بهترین قابلیت تحویل. صندوق ورودی و همهٔ DNS دریافت دقیقاً همان میمانند.",
+ "pathTitle": "مسیر خروجی",
+ "setUpRelay": "راهاندازی رله",
+ "directActive": "نامهٔ خروجی مستقیماً این سرور را ترک میکند. چیزی برای پیکربندی نیست — اگر قابلیت تحویل مشکل شد رله را بالا انتخاب کنید.",
+ "switchToDirect": "تغییر به تحویل مستقیم",
+ "switchToDirectHint": "smarthost و اعتبار ذخیرهشدهاش را حذف میکند، و include مربوط به SPF رله را از DNS برمیدارد. نامهٔ خروجی دوباره مستقیم به سرور هر گیرنده میرود.",
+ "statusOn": "رله فعال",
+ "statusOff": "تحویل مستقیم (پیشفرض)",
+ "sendersTitle": "کدام فرستندهها رله شوند؟",
+ "scopeAll": "همهٔ فرستندهها",
+ "scopeSelected": "فرستندههای انتخابشده",
+ "scopeAllHint": "هر دامنه روی این سرور از طریق رله میفرستد.",
+ "scopeSelectedHint": "فقط فرستندههایی که انتخاب میکنید رله میشوند؛ بقیه مستقیم تحویل میدهند.",
+ "sendersDomains": "دامنههای کامل",
+ "sendersAddresses": "فرستندههای تکی",
+ "sendersAddressesHint": "یک صندوق را بدون رلهٔ کل دامنهاش رله کنید — contact@example.com میتواند از طریق ارائهدهنده بفرستد در حالی که هنوز اینجا از طریق IMAP دریافت میکند.",
+ "senderPlaceholder": "contact@example.com",
+ "addSender": "افزودن",
+ "removeSender": "حذف",
+ "senderInvalid": "یک آدرس ساده مثل contact@example.com وارد کنید — بدون نام نمایشی.",
+ "noSenders": "حداقل یک دامنه یا آدرس فرستنده برای رله انتخاب کنید.",
+ "noDomains": "هنوز دامنهای روی این سرور پیدا نشد.",
+ "credentialsTitle": "ارائهدهنده و اعتبارنامه",
+ "provider": "ارائهدهنده",
+ "regionFor": "منطقهٔ {provider}",
+ "host": "میزبان SMTP",
+ "port": "پورت",
+ "username": "نام کاربری SMTP",
+ "password": "رمز عبور SMTP",
+ "passwordKeep": "برای نگه داشتن رمز فعلی خالی بگذارید",
+ "port465Blocked": "پورت ۴۶۵ (TLS ضمنی) تنظیم سراسری سرور است، پس وقتی فقط بعضی فرستندهها رله میشوند نمیتوان از آن استفاده کرد. از پورت STARTTLS ارائهدهنده استفاده کنید — معمولاً ۵۸۷.",
+ "spfIncludeLabel": "include مربوط به SPF",
+ "spfIncludeHint": "مکانیزم include: را که ارائهدهنده میگوید منتشر کنید بچسبانید. اگر خالی بماند رکورد SPF را دست نمیزنیم — اما نامهٔ رلهشده از میزبانی که SPF فهرست نکرده در SPF شکست میخورد.",
+ "spfNone": "هیچکدام",
+ "mailFromDomain": "دامنهٔ سفارشی MAIL FROM",
+ "mailFromHint": "اختیاری. زیردامنهای مثل bounce.example.com برای همترازی SPF/برگشت. آن را در ارائهدهنده هم پیکربندی کنید.",
+ "identityTitle": "هویت {provider} بهازای دامنه",
+ "identityHint": "{provider} هر دامنهٔ ارسال را جداگانه تأیید میکند. رکوردهای هر دامنهٔ رلهشده را بچسبانید (اختیاری — amavis از قبل امضا میکند، پس DMARC بدون آنها همتراز میشود، اما ارائهدهنده قبل از ارسال نیاز به تأیید دامنه دارد).",
+ "identityPrimary": "اصلی",
+ "dkimRecords": "رکوردهای DKIM مربوط به {provider}",
+ "dkimRecordsHint": "رکوردهای CNAME را که ارائهدهنده نشان میدهد بچسبانید (SES: کنسول → Verified identities → دامنهٔ شما → DKIM). آنها را در زبانهٔ DNS رندر و تأیید میکنیم.",
+ "dkimName": "نام CNAME",
+ "dkimValue": "مقدار CNAME",
+ "summaryTitle": "خلاصه",
+ "summaryTarget": "Smarthost",
+ "summarySenders": "فرستندهها",
+ "summaryUpdated": "آخرین ذخیره",
+ "guidanceTitle": "قبل از کار کردن این",
+ "guidanceVerify": "دامنه را در کنسول Amazon SES تأیید کنید.",
+ "guidanceSandbox": "دسترسی تولید بخواهید — SES در sandbox شروع میشود که فقط به آدرسهای تأییدشده میفرستد.",
+ "guidanceCreds": "از اعتبار SMTP مربوط به SES (کاربر IAM SMTP) استفاده کنید — نه کلیدهای دسترسی AWS.",
+ "guidanceDns": "رکوردهای DNS را در زبانهٔ DNS منتشر کنید، سپس برای تأیید دوباره اسکن کنید.",
+ "guidanceSystem": "ایمیل سیستم (بازنشانی رمز، دعوتها، اعلانها) هم از این رله میرود — مگر فرستندهٔ جداگانهای در تنظیمات → ایمیل تنظیم کرده باشید.",
+ "testLiveNote": "از مسیری که الان زنده است میفرستد، نه تغییرات ذخیرهنشدهٔ بالا.",
+ "save": "فعالسازی رله",
+ "update": "ذخیرهٔ تغییرات",
+ "disable": "غیرفعالسازی رله",
+ "saved": "رلهٔ خروجی بهروز شد.",
+ "disabled": "رله غیرفعال شد — برگشت به تحویل مستقیم.",
+ "saveFailed": "نتوانستیم رله را بهروز کنیم.",
+ "disableFailed": "نتوانستیم رله را غیرفعال کنیم.",
+ "dnsHint": "رکوردهای hop ارسال ارائهدهنده حالا در زبانهٔ DNS ظاهر میشوند. برای قابلیت تحویل کامل آنها را منتشر و تأیید کنید."
+ },
+ "overview": {
+ "mailServer": "سرور ایمیل",
+ "protocolDetails": "جزئیات پروتکل →",
+ "hostname": "نام میزبان",
+ "webmailAt": "وبمیل در",
+ "openWebmail": "باز کردن وبمیل",
+ "deployWebmail": "استقرار وبمیل",
+ "legacyWebmail": "توسط Openship قدیمیتر استقرار یافته — میتواند با اپ وبمیل جایگزین شود که با انتشارات شما بهروز میشود.",
+ "upgradeWebmail": "جایگزینی وبمیل",
+ "setupGuides": "راهنماهای راهاندازی",
+ "setupGuidesDesc": "گامبهگام برای هر روش رایج استفاده از این صندوق — از تلفن تا کدبیس. در زبانهٔ جدید باز میشود.",
+ "guides": {
+ "iosTitle": "ایمیل iOS و macOS",
+ "iosSubtitle": "بهعنوان IMAP روی iPhone / iPad / Mac اضافه کنید",
+ "androidTitle": "اپ Gmail اندروید",
+ "androidSubtitle": "بهعنوان حساب IMAP شخص ثالث اضافه کنید",
+ "desktopTitle": "کلاینتهای دسکتاپ",
+ "desktopSubtitle": "Thunderbird، Outlook، Spark، K-9",
+ "codeTitle": "ارسال از طریق کد",
+ "codeSubtitle": "Node.js، Python، هر جا SMTP کار کند"
+ },
+ "mailStats": "آمار ایمیل",
+ "statsFailed": "آمار ناموفق بود",
+ "domains": "دامنهها",
+ "mailboxes": "صندوقهای پستی",
+ "aliases": "نامهای مستعار",
+ "storage": "ذخیرهسازی",
+ "messages": "پیامها",
+ "totalSuffix": "{count} جمع",
+ "quickActions": "اقدامات سریع",
+ "addMailbox": "افزودن صندوق پستی",
+ "addDomain": "افزودن دامنه",
+ "reviewDns": "بازبینی رکوردهای DNS"
+ },
+ "webmail": {
+ "title": "وبمیل",
+ "subtitle": "کلاینت ایمیلی که کاربران در آن وارد میشوند.",
+ "opening": "در حال باز کردن پروژهٔ وبمیل…",
+ "noServerTitle": "هنوز سرور ایمیلی نیست",
+ "noServerBody": "وبمیل فقط یک کلاینت است — برای ورود به سرور ایمیل نیاز دارد. ابتدا یکی راهاندازی کنید، سپس برای نصب برگردید.",
+ "installTitle": "وبمیل نصب نشده",
+ "installBody": "کلاینت وبمیل را استقرار دهید تا صندوقها در مرورگر خوانده شوند. بهعنوان یک پروژهٔ معمولی Openship نصب میشود، پس مثل هر استقرار دیگری آن را زیر پروژهها مدیریت میکنید.",
+ "adoptTitle": "وبمیل اینجا مدیریت نمیشود",
+ "chooseTitle": "کدام سرور ایمیل؟",
+ "chooseBody": "هر سرور ایمیل وبمیل خودش را دارد. دامنهای را که میخواهید باز کنید انتخاب کنید.",
+ "incompleteTitle": "سرور ایمیل هنوز آماده نیست",
+ "incompleteBody": "{server} هنوز در حال راهاندازی است. وبمیل به آن وارد میشود، پس ابتدا سرور ایمیل را تمام کنید — سپس وبمیل را از اینجا نصب کنید.",
+ "incompleteAction": "تمام کردن راهاندازی ایمیل",
+ "runningNote": "وبمیلی از قبل در {hostname} اجرا میشود اما اینجا چیزی آن را مدیریت نمیکند. نصب آن را به پروژه میآورد بهجای ایستادن دومی."
+ },
+ "domains": {
+ "heading": "دامنهها",
+ "description": "هر ردیف به یک ردیف در vmail.domain روی VPS ایمیل نگاشت میشود. دامنههای اضافی وقتی رکورد MX آنها به اینجا اشاره کند نامه میپذیرند.",
+ "addDomain": "افزودن دامنه",
+ "editAction": "ویرایش",
+ "deleteAction": "حذف",
+ "colDomain": "دامنه",
+ "colMailboxes": "صندوقهای پستی",
+ "colAliases": "نامهای مستعار",
+ "colQuota": "سهمیهٔ پیشفرض",
+ "colStatus": "وضعیت",
+ "active": "فعال",
+ "disabled": "غیرفعال",
+ "primary": "اصلی",
+ "pendingTitle": "انتشار رکوردهای DNS برای {domain}",
+ "pendingDescBefore": "نامه میتواند به ",
+ "pendingDescAfter": " جریان یابد وقتی اینها زنده شوند. رکورد MX به سرور ایمیل موجود شما اشاره میکند؛ DKIM هنگام افزودن دامنه خودکار تولید شد. آنها را در ارائهدهندهٔ DNS اضافه کنید، سپس کلیک کنید ",
+ "pendingDescAction": "رکوردها را تنظیم کردم — ادامه",
+ "pendingDescEnd": ".",
+ "emptyTitle": "هنوز دامنهای نیست",
+ "emptyDesc": "دامنهای اضافه کنید تا صندوقها را زیر آن میزبانی کنید. مطمئن شوید رکورد MX آن به این سرور ایمیل اشاره میکند.",
+ "acknowledgeFailed": "تأیید رکوردهای DNS ناموفق بود",
+ "loadFailed": "بارگذاری دامنهها ناموفق بود",
+ "create": {
+ "title": "افزودن دامنه",
+ "description": "ورودی جدیدی به vmail.domain روی VPS ایمیل اضافه میکند. قبل از ارسال نامه از این دامنه مطمئن شوید رکورد MX به سرور ایمیل اشاره میکند.",
+ "submit": "ساخت دامنه",
+ "submitting": "در حال ساخت…",
+ "domainLabel": "دامنه",
+ "domainHint": "مثلاً acme.com — بدون پروتکل، بدون مسیر.",
+ "descLabel": "توضیح",
+ "descHint": "اختیاری — فقط در داشبورد نشان داده میشود.",
+ "descPlaceholder": "بازاریابی Acme Corp",
+ "quotaLabel": "سهمیهٔ پیشفرض صندوق (GB)",
+ "quotaHint": "اختیاری. پیشفرض صندوقهای جدید این دامنه؛ میتواند بهازای صندوق بازنویسی شود. برای نامحدود خالی بگذارید."
+ },
+ "editForm": {
+ "title": "ویرایش {domain}",
+ "submit": "ذخیرهٔ تغییرات",
+ "submitting": "در حال ذخیره…",
+ "descLabel": "توضیح",
+ "quotaLabel": "سهمیهٔ پیشفرض صندوق (GB)",
+ "quotaHint": "برای نامحدود خالی یا ۰ بگذارید.",
+ "activeLabel": "فعال",
+ "activeDesc": "وقتی تیک نباشد، نامهٔ خطاب به این دامنه در دروازهٔ MX رد میشود."
+ },
+ "deleteForm": {
+ "title": "{domain} حذف شود؟",
+ "descDependents": "این دامنه هنوز {parts} دارد. برای حذف آنها هم کادر زیر را تیک بزنید — فایلهای صندوق روی دیسک حذف میشوند و برگشتناپذیر است.",
+ "descSimple": "ردیف دامنه و هر نگاشت ادمین را حذف میکند. نامه به این دامنه بلافاصله رد میشود.",
+ "submitWithParts": "حذف دامنه + {parts}",
+ "submit": "حذف دامنه",
+ "submitting": "در حال حذف…",
+ "alsoDelete": "همچنین {parts} را حذف کن",
+ "alsoDeleteDesc": "هر صندوق زیر این دامنه (ردیفهای DB و فایلهای Maildir روی دیسک) را همراه همهٔ نامهای مستعار برای همیشه حذف میکند. برگشتناپذیر است.",
+ "errorDependents": "دامنه هنوز {mailboxes} صندوق و {aliases} نام مستعار دارد. «همچنین حذف…» را تیک بزنید، یا ابتدا دستی حذف کنید.",
+ "mailboxOne": "{count} صندوق",
+ "mailboxOther": "{count} صندوق",
+ "aliasOne": "{count} نام مستعار",
+ "aliasOther": "{count} نام مستعار",
+ "partsJoin": " و "
+ }
+ },
+ "dns": {
+ "heading": "رکوردهای DNS",
+ "description": "رکوردهایی که هر دامنه برای تحویل ایمیل در ارائهدهندهٔ DNS نیاز دارد. دامنهای را انتخاب کنید تا رکوردهایش را ببینید و تأیید کنید منتشر شدهاند.",
+ "domainLabel": "دامنه",
+ "loading": "در حال بارگذاری…",
+ "primary": "اصلی",
+ "recordsTitle": "رکوردها برای انتشار",
+ "recordsDesc": "اینها را در ارائهدهندهٔ DNS برای {domain} منتشر کنید.",
+ "recordsEmpty": "جادوگر نصب رکوردها را در گام DKIM تولید میکند؛ دامنههای اضافی هنگام افزودن رکورد میگیرند.",
+ "noRecordsFor": "رکورد DNS ثبتشدهای برای {domain} نیست.",
+ "checkTitle": "بررسی رکوردهای منتشرشده",
+ "checkDesc": "جستجوی زندهٔ DNS عمومی برای {domain}، مقایسه با رکوردهای بالا.",
+ "rescan": "اسکن دوباره",
+ "verify": "تأیید",
+ "runLookupBefore": "یک جستجوی زندهٔ DNS برای ",
+ "runLookupAfter": " اجرا کنید و با رکوردهای بالا مقایسه کنید.",
+ "noRecordsVerify": "رکوردی برای تأیید {domain} ثبت نشده.",
+ "scanFailed": "اسکن DNS ناموفق بود",
+ "expected": "انتظار",
+ "actual": "واقعی",
+ "noRecord": "(بدون رکورد)",
+ "pass": "قبول",
+ "warning": "هشدار",
+ "fail": "رد",
+ "unknown": "نامعلوم"
+ },
+ "mailboxes": {
+ "heading": "صندوقهای پستی",
+ "description": "هر ردیف یک حساب IMAP/SMTP روی سرور ایمیل است. رمزها قبل از ذخیره با doveadm SSHA512 هش میشوند.",
+ "addMailbox": "افزودن صندوق پستی",
+ "domainLabel": "دامنه",
+ "loading": "در حال بارگذاری…",
+ "activeCount": "{active} از {total} فعال",
+ "colMailbox": "صندوق پستی",
+ "colQuota": "سهمیه",
+ "colStatus": "وضعیت",
+ "unlimited": "نامحدود",
+ "active": "فعال",
+ "disabled": "غیرفعال",
+ "postmaster": "Postmaster",
+ "editAction": "ویرایش",
+ "deleteAction": "حذف",
+ "loadDomainsFailed": "بارگذاری دامنهها ناموفق بود",
+ "loadFailed": "بارگذاری صندوقها ناموفق بود",
+ "emptyTitle": "هنوز صندوق پستی نیست",
+ "emptyDesc": "اولین صندوق را برای {domain} بسازید. رمزها با doveadm SSHA512 هش میشوند — فقط هش روی دیسک ذخیره میشود.",
+ "create": {
+ "title": "صندوق جدید · {domain}",
+ "description": "یک حساب IMAP/SMTP روی سرور ایمیل میسازد. رمز روی VPS با doveadm SSHA512 هش میشود — فقط هش ذخیره میشود.",
+ "submit": "ساخت صندوق",
+ "submitting": "در حال ساخت…",
+ "localPartLabel": "بخش محلی",
+ "localPartHint": "بخش قبل از @. حروف کوچک، الفباییعددی.",
+ "fullNameLabel": "نام کامل",
+ "fullNameHint": "نام نمایشی در کلاینتهای ایمیل. اختیاری.",
+ "passwordLabel": "رمز عبور",
+ "passwordHint": "حداقل ۸ نویسه. یا یکی تولید کنید.",
+ "generate": "تولید",
+ "quotaLabel": "سهمیه (GB)",
+ "quotaHint": "۰ یا خالی = نامحدود.",
+ "hide": "پنهان",
+ "show": "نمایش",
+ "copy": "کپی",
+ "copyPassword": "کپی رمز عبور"
+ },
+ "editForm": {
+ "title": "ویرایش {username}",
+ "submit": "ذخیرهٔ تغییرات",
+ "submitting": "در حال ذخیره…",
+ "fullNameLabel": "نام کامل",
+ "newPasswordLabel": "رمز جدید",
+ "newPasswordHint": "برای نگه داشتن فعلی خالی بگذارید. اگر تنظیم شود حداقل ۸ نویسه.",
+ "quotaLabel": "سهمیه (GB)",
+ "quotaHint": "۰ یا خالی = نامحدود.",
+ "activeLabel": "فعال",
+ "activeDesc": "وقتی تیک نباشد، ورود IMAP شکست میخورد و نامهٔ ورودی برمیگردد."
+ },
+ "deleteForm": {
+ "title": "{username} حذف شود؟",
+ "descHard": "حذف سخت: ردیفهای DB و Maildir روی دیسک را حذف میکند. برگشتناپذیر است.",
+ "descSoft": "حذف نرم: active = 0 میگذارد و ردیفی در vmail.deleted_mailboxes درج میکند. cron پاکسازی سرور ایمیل بعداً آن را برمیدارد. Maildir تا آن موقع روی دیسک میماند.",
+ "submitHard": "حذف سخت صندوق",
+ "submitSoft": "حذف نرم صندوق",
+ "submitting": "در حال حذف…",
+ "hardDeleteLabel": "حذف سخت",
+ "hardDeleteDesc": "ردیفهای DB و فایلهای Maildir را فوری حذف کن. برگشتناپذیر است.",
+ "postmasterNote": "صندوق postmaster در برابر حذف سخت محافظت شده — فقط حذف نرم در دسترس است، تا حساب راهانداز سرور ایمیل سالم بماند."
+ }
+ },
+ "aliases": {
+ "heading": "نامهای مستعار",
+ "description": "نامه را از یک آدرس به دیگری هدایت کنید، یا هر آدرس بیتطبیق یک دامنه را بگیرید. بهمحض ذخیره زنده است — نیازی به راهاندازی مجدد نیست.",
+ "addAlias": "افزودن نام مستعار",
+ "domainLabel": "دامنه",
+ "loading": "در حال بارگذاری…",
+ "activeCount": "{active} از {total} فعال",
+ "colSource": "منبع",
+ "colDestination": "هدایت به",
+ "colType": "نوع",
+ "colStatus": "وضعیت",
+ "typeAlias": "نام مستعار",
+ "typeCatchAll": "گرفتن همه",
+ "active": "فعال",
+ "disabled": "غیرفعال",
+ "activateAction": "فعالسازی",
+ "deactivateAction": "غیرفعالسازی",
+ "deleteAction": "حذف",
+ "loadDomainsFailed": "بارگذاری دامنهها ناموفق بود",
+ "loadFailed": "بارگذاری نامهای مستعار ناموفق بود",
+ "emptyTitle": "هنوز نام مستعاری نیست",
+ "emptyDesc": "آدرسی را هدایت کنید، یا گرفتن همه را برای {domain} راهاندازی کنید.",
+ "create": {
+ "title": "نام مستعار جدید · {domain}",
+ "description": "ردیفی در جدول هدایت سرور ایمیل میسازد. Postfix آن را زنده میخواند — نیازی به راهاندازی مجدد نیست.",
+ "submit": "ساخت نام مستعار",
+ "submitting": "در حال ساخت…",
+ "catchAllLabel": "گرفتن همه برای این دامنه",
+ "catchAllHint": "هر آدرسی در {domain} را که با صندوق واقعی یا نام مستعار دیگری جور نیست هدایت میکند. هر گرفتن همهٔ موجود این دامنه را جایگزین میکند.",
+ "localPartLabel": "بخش محلی",
+ "localPartHint": "بخش قبل از @. حروف کوچک، الفباییعددی.",
+ "destinationLabel": "هدایت به",
+ "destinationHint": "هر آدرس ایمیل معتبر — صندوقی روی این سرور، یا خارجی (مثلاً Gmail)."
+ },
+ "deleteForm": {
+ "title": "این نام مستعار حذف شود؟",
+ "sourceCatchAll": "گرفتن همه برای {domain}",
+ "description": "ردیف هدایت را فوری حذف میکند. نامه به {source} برمیگردد یا به قانون تطبیق بعدی میافتد، از پیام بعدی مؤثر است.",
+ "submit": "حذف نام مستعار",
+ "submitting": "در حال حذف…"
+ },
+ "toast": {
+ "mailboxConflict": "{address} یک صندوق واقعی است — ساخت نام مستعار اینجا نامهاش را منحرف میکند. بهجایش صندوق را ویرایش یا حذف کنید.",
+ "alreadyExists": "آن هدایت دقیق از قبل وجود دارد."
+ }
+ },
+ "health": {
+ "heading": "سلامت",
+ "description": "وضعیت زندهٔ دیمون + اسکن DNS. دیمونها هر ۱۰ ثانیه تازه میشوند؛ اسکن DNS یک بار هنگام باز شدن و دوباره هر بار که اسکن دوباره را میزنید اجرا میشود.",
+ "healthCheckFailed": "بررسی سلامت ناموفق بود",
+ "scanFailed": "اسکن DNS ناموفق بود",
+ "daemonsTitle": "دیمونها",
+ "daemonsDesc": "فرآیندهای سرور ایمیل که systemd روی VPS مدیریت میکند.",
+ "updated": "بهروزشده {time}",
+ "dnsScanTitle": "اسکن DNS",
+ "dnsScanDescFor": "بررسی زندهٔ DNS عمومی هر رکوردی که برای {domain} انتظار داریم.",
+ "dnsScanDesc": "بررسی زندهٔ DNS عمومی هر رکوردی که این سرور انتظار دارد.",
+ "rescan": "اسکن دوباره",
+ "dnsEmpty": "هنوز رکورد DNS ثبت نشده. جادوگر نصب را تمام کنید تا اسکن پر شود.",
+ "delivery": {
+ "title": "تحویل خروجی",
+ "desc": "این سرور نامه را کجا میسپارد، و آیا چیزی در راه خروجی گیر کرده.",
+ "sendPath": "مسیر ارسال",
+ "direct": "مستقیم به گیرندهها",
+ "directHint": "پیامها مستقیم از این سرور به سرور ایمیل هر گیرنده میروند.",
+ "relay": "رله از طریق {host}",
+ "relayAll": "هر فرستنده روی این سرور از طریق رله میفرستد.",
+ "relaySelected": "فقط فرستندههای انتخابشده رله میشوند: {domains}",
+ "queue": "صف",
+ "queueEmpty": "خالی — چیزی منتظر خروج نیست.",
+ "queueOne": "{count} پیام منتظر خروج است.",
+ "queueOther": "{count} پیام منتظر خروج هستند.",
+ "sampled": "صف بزرگتر از پنجرهای است که خواندیم، پس دلایل زیر جدیدترینها هستند.",
+ "reasons": "چرا نامه به تأخیر میافتد",
+ "affectedOne": "{count} پیام",
+ "affectedOther": "{count} پیام",
+ "kind": {
+ "auth": "احراز هویت رد شد",
+ "tls": "رمزنگاری ناموفق بود",
+ "network": "نتوانست متصل شود",
+ "rejected": "توسط گیرنده رد شد",
+ "other": "به تأخیر افتاده"
+ },
+ "hint": {
+ "authRelay": "رله این اعتبار را رد کرد. نام کاربری و رمز SMTP را در زبانهٔ ارسال دوباره وارد کنید.",
+ "auth": "hop بعدی احراز هویت را رد کرد.",
+ "tls": "TLS با hop بعدی مذاکره نشد.",
+ "network": "این سرور نتوانست به hop بعدی برسد. بررسی کنید پورتهای خروجی ۲۵ و ۵۸۷ باز باشند.",
+ "rejected": "گیرنده فعلاً پیام را رد کرد. Postfix به تلاش ادامه میدهد.",
+ "other": "Postfix طبق زمانبندی خودش دوباره تلاش میکند."
+ },
+ "status": {
+ "ok": "در حال ارسال",
+ "warn": "تأخیری",
+ "fail": "ارسال نمیشود",
+ "unknown": "نامعلوم"
+ }
+ },
+ "logs": "گزارشها",
+ "openLogs": "باز کردن گزارشها",
+ "up": "بالا {time}",
+ "optional": "اختیاری",
+ "daemonHint": {
+ "fatal": "supervisord تسلیم شده — خودش دوباره تلاش نمیکند. از منو راهاندازی مجدد کنید.",
+ "backoff": "مدام کرش میکند؛ supervisord هنوز تلاش میکند. برای علت گزارشها را ببینید."
+ },
+ "menu": {
+ "restart": "راهاندازی مجدد",
+ "restarting": "در حال راهاندازی مجدد…",
+ "stop": "توقف",
+ "stopping": "در حال توقف…",
+ "start": "شروع",
+ "starting": "در حال شروع…"
+ },
+ "toast": {
+ "started": "{label} شروع شد",
+ "stopped": "{label} متوقف شد",
+ "restarted": "{label} راهاندازی مجدد شد",
+ "startFailed": "شروع ناموفق بود",
+ "stopFailed": "توقف ناموفق بود",
+ "restartFailed": "راهاندازی مجدد ناموفق بود",
+ "startFailedTitle": "شروع {label} ناموفق بود",
+ "stopFailedTitle": "توقف {label} ناموفق بود",
+ "restartFailedTitle": "راهاندازی مجدد {label} ناموفق بود",
+ "notConfirmedTitle": "{label} بالا نیامده",
+ "notConfirmed": "ناظر درخواست را پذیرفت، اما {label} در وضعیت {state} است. برای علت گزارشها را باز کنید."
+ },
+ "daemonStatus": {
+ "running": "در حال اجرا",
+ "starting": "در حال شروع",
+ "stopping": "در حال توقف",
+ "stopped": "متوقف",
+ "failed": "ناموفق",
+ "crashed": "کرششده",
+ "missing": "گمشده",
+ "unknown": "نامعلوم"
+ },
+ "dnsStatus": {
+ "pass": "قبول",
+ "warning": "هشدار",
+ "fail": "رد",
+ "unknown": "نامعلوم"
+ },
+ "expected": "انتظار",
+ "actual": "واقعی",
+ "noRecord": "(بدون رکورد)",
+ "summary": {
+ "allGoodLabel": "همهٔ سیستمها عملیاتیاند",
+ "allGoodSub": "دیمونها اجرا میشوند و هر رکورد DNS درست منتشر شده.",
+ "almostLabel": "تقریباً آماده",
+ "almostSubOne": "{count} رکورد DNS اختیاری میتواند بهتر شود — اسکن DNS را پایین ببینید.",
+ "almostSubOther": "{count} رکورد DNS اختیاری میتوانند بهتر شوند — اسکن DNS را پایین ببینید.",
+ "notInstalledLabel": "{names} نصب نشده",
+ "notInstalledSubOne": "ایمیل بدون آن هنوز کار میکند؛ فقط اگر واقعاً به این جزء نیاز دارید نصب کنید.",
+ "notInstalledSubOther": "ایمیل بدون آنها هنوز کار میکند؛ فقط اگر واقعاً به این اجزا نیاز دارید نصب کنید.",
+ "issuesLabel": "مشکلات نیاز به توجه دارند",
+ "partDown": "{names} پایین",
+ "partNotInstalled": "{names} نصب نشده",
+ "partDnsOne": "{count} رکورد DNS گمشده",
+ "partDnsOther": "{count} رکورد DNS گمشده",
+ "partDelivery": "نامهٔ خروجی تحویل نمیشود",
+ "degradedLabel": "کار میکند، با حفاظت کاهشیافته",
+ "partScanning": "{names} اجرا نمیشود — نامهٔ جدید بدون اسکن ویروس تحویل میشود",
+ "partSignatures": "{names} اجرا نمیشود — امضاهای ویروس دیگر بهروز نمیشوند، پس نامه در برابر مجموعهٔ امضای کهنه اسکن میشود",
+ "partAdvisory": "{names} اجرا نمیشود",
+ "almostSubQueueOne": "{count} پیام منتظر خروج است — تحویل خروجی را پایین ببینید.",
+ "almostSubQueueOther": "{count} پیام منتظر خروج هستند — تحویل خروجی را پایین ببینید."
+ },
+ "time": {
+ "secondsAgo": "{n}ثانیه پیش",
+ "minutesAgo": "{n}دقیقه پیش",
+ "hoursAgo": "{n}ساعت پیش",
+ "daysAgo": "{n}روز پیش"
+ }
+ },
+ "backup": {
+ "heading": "پشتیبان",
+ "description": "از این سرور ایمیل در یکی از مقاصد پشتیبان اسنپشات بگیرید. برای جابهجایی سرور، اینجا پشتیبان بگیرید، مقصد را با جادوگر نصب راهاندازی کنید، سپس این پشتیبان را روی آن بازیابی کنید.",
+ "schedules": {
+ "manual": "فقط دستی",
+ "daily": "روزانه",
+ "weekly": "هفتگی",
+ "monthly": "ماهانه",
+ "custom": "سفارشی (cron)"
+ },
+ "whatTitle": "چه چیزی پشتیبان گرفته شود",
+ "whatDesc": "حسابها و احراز هویت همیشه شاملاند؛ دادهٔ پیام و کلیدها اختیاریاند.",
+ "accountsTitle": "حسابها، دامنهها و نامهای مستعار",
+ "accountsDesc": "صندوقها (با هش رمز)، دامنهها، و نامهای مستعار — پایگاهدادهٔ vmail.",
+ "messageTitle": "دادهٔ پیام صندوق",
+ "messageDesc": "هر ایمیل ذخیرهشده (maildirهای /var/vmail). میتواند بزرگ باشد.",
+ "keysTitle": "کلیدهای DKIM و اسرار",
+ "keysDesc": "کلیدهای DKIM + پیکربندی + mail-state.json. DKIM را هنگام بازیابی معتبر نگه میدارد — اما آرشیو سپس کلیدهای خصوصی و اسرار را نگه میدارد.",
+ "keysWarning": "پشتیبان شامل هش رمز، کلیدهای خصوصی DKIM، و اسرار متنواضح خواهد بود. مقصد را خصوصی نگه دارید.",
+ "destTitle": "مقصد و زمانبندی",
+ "destDesc": "پشتیبانها به مقصدی که در صفحهٔ پشتیبانها مدیریت میکنید استریم میشوند.",
+ "noDest": "هنوز مقصد پشتیبانی نیست. یکی را (مثلاً سرور موجود) در صفحهٔ پشتیبانها اضافه کنید.",
+ "destinationLabel": "مقصد",
+ "autoLabel": "پشتیبانهای خودکار",
+ "timeLabel": "زمان",
+ "timeHint": "زمان سرور",
+ "weekdayLabel": "روز هفته",
+ "dayOfMonthLabel": "روز ماه",
+ "cronLabel": "عبارت cron",
+ "cronHint": "پنج فیلد، در منطقهٔ زمانی سرور ارزیابی میشود.",
+ "scheduleJobHint": "پشتیبانهای خودکار بهعنوان کار زمانبندیشده اجرا میشوند.",
+ "manageDest": "مدیریت",
+ "retentionTitle": "نگهداری",
+ "retentionDesc": "چقدر تاریخچه در مقصد نگه داشته شود. پشتیبانهای قدیمیتر توسط پاکسازی روزانه حذف میشوند، فایلها و همه.",
+ "keepCountLabel": "نگه داشتن آخرین",
+ "keepCountUnit": "پشتیبان",
+ "keepDaysLabel": "حذف بعد از",
+ "keepDaysUnit": "روز",
+ "retentionNoLimit": "بدون محدودیت",
+ "retentionUnlimitedWarning": "هیچ چیز هرگز حذف نمیشود — این مقصد مدام رشد میکند.",
+ "weekdays": {
+ "sun": "یکشنبه",
+ "mon": "دوشنبه",
+ "tue": "سهشنبه",
+ "wed": "چهارشنبه",
+ "thu": "پنجشنبه",
+ "fri": "جمعه",
+ "sat": "شنبه"
+ },
+ "saveSettings": "ذخیرهٔ تنظیمات",
+ "backupNow": "الان پشتیبان بگیر",
+ "recentTitle": "پشتیبانهای اخیر",
+ "noBackups": "هنوز پشتیبانی نیست.",
+ "restore": "بازیابی",
+ "migrate": "مهاجرت",
+ "toastTitle": "پشتیبان",
+ "pickDest": "ابتدا یک مقصد پشتیبان انتخاب کنید.",
+ "scheduleIncomplete": "زمانی برای این زمانبندی انتخاب کنید، یا به دستی برگردانید.",
+ "saved": "تنظیمات پشتیبان ذخیره شد.",
+ "saveFailed": "نتوانستیم تنظیمات پشتیبان را ذخیره کنیم",
+ "started": "پشتیبان شروع شد. هنگام اجرا پایین ظاهر میشود.",
+ "startFailed": "نتوانستیم پشتیبان را شروع کنیم"
+ },
+ "advanced": {
+ "resetTitle": "وضعیت روی سرور بازنشانی شود؟",
+ "resetDesc": "ردیابی نصب این Openship را پاک میکند: فایل وضعیت روی سرور (/root/.openship/mail-state.json) و ردیف رجیستری سرور ایمیل. پشتهٔ ایمیل در حال اجرا دست نمیخورد — هر صندوق، پیام و صف سالم میماند — اما برای مدیریت دوباره باید جادوگر راهاندازی را دوباره اجرا کنید (حذف فایل وضعیت را نگه میدارد تا فقط دوباره بپذیرید).",
+ "resetSubmit": "بازنشانی وضعیت",
+ "resetSubmitting": "در حال بازنشانی…",
+ "resetFailed": "بازنشانی ناموفق بود",
+ "resetBox": "بعد از این، صفحهٔ /emails دوباره جادوگر نصب را برای این سرور نشان میدهد. سپس میتوانید از گام ۱ دوباره اجرا کنید یا از گام مشخصی ادامه دهید.",
+ "forgetTitle": "این سرور ایمیل حذف شود؟",
+ "forgetDesc": "این سرور را فقط از فهرست ایمیل Openship حذف میکند. پشتهٔ ایمیل به اجرا ادامه میدهد و چیزی حذف نصب نمیشود — هیچ صندوق، پیام یا DNS دست نمیخورد. هر زمان با اسکن نصب موجود دوباره اضافه کنید.",
+ "forgetSubmit": "حذف از فهرست",
+ "forgetSubmitting": "در حال حذف…",
+ "forgetFailed": "حذف ناموفق بود",
+ "forgetBox": "برای پاک کردن ورودی کهنه یا اشتباهعلامتخورده استفاده کنید. چون نصب روی سرور دستنخورده میماند، پذیرش دوباره بعداً مدیریت کامل را بدون نصب مجدد بازیابی میکند.",
+ "protocolTitle": "تنظیمات پروتکل",
+ "protocolDesc": "میزبان، پورت و رمزنگاری برای ورودی (IMAP) و خروجی (SMTP). هنگام پیکربندی دستی کلاینت ایمیل از اینها استفاده کنید — بیشتر کلاینتها میتوانند آنها را فقط از آدرس ایمیل کشف کنند.",
+ "incoming": "ورودی · IMAP",
+ "outgoing": "خروجی · SMTP",
+ "host": "میزبان",
+ "port": "پورت",
+ "security": "امنیت",
+ "protocolNote": {
+ "p1": "نام کاربری روی هر دو سرور ",
+ "emailAddress": "آدرس ایمیل کامل",
+ "p2": " شماست — مثلاً ",
+ "p3": ". رمز را از ",
+ "credPath": "نمای کلی → اعتبار Postmaster → تغییر رمز",
+ "p4": " تنظیم کنید؛ Openship هرگز متنواضح را ذخیره نمیکند، پس برای ذخیره از مدیر رمز (1Password، Bitwarden و غیره) استفاده کنید."
+ },
+ "dangerTitle": "منطقهٔ خطر",
+ "dangerDesc": "این اعمال میتوانند سرور ایمیل در حال کار را مختل کنند یا وضعیت ردیابی مهم را پاک کنند. قبل از کلیک توضیح هر کارت را بخوانید.",
+ "rerunTitle": "اجرای دوبارهٔ جادوگر راهاندازی",
+ "rerunDesc": "جادوگر نصب را به این سرور اشاره میدهد. مفید بعد از تغییر DNS، تغییر نام دامنه، یا تلاش دوباره برای گام ناموفق. جادوگر نصب موجود را تشخیص میدهد و بهجای پاک کردن وضعیت، تلاش دوبارهٔ بهازای گام پیشنهاد میکند.",
+ "openWizard": "باز کردن جادوگر",
+ "resetCardTitle": "بازنشانی وضعیت روی سرور",
+ "resetCardDesc": "ردیابی نصب این Openship را حذف میکند — فایل وضعیت روی سرور (/root/.openship/mail-state.json) و ردیف رجیستری. پشتهٔ ایمیل را حذف نصب نمیکند و به هیچ صندوقی دست نمیزند؛ سرور به اجرا ادامه میدهد، اما برای مدیریت دوباره جادوگر راهاندازی را اجرا میکنید. بعد از پاکسازی دستی یا ایمیج دوباره وقتی ردیابی از واقعیت فاصله گرفته استفاده کنید.",
+ "resetStateBtn": "بازنشانی وضعیت",
+ "removeTitle": "حذف سرور ایمیل",
+ "removeDesc": "مدیریت این سرور را متوقف میکند — فقط از فهرست /emails حذف میشود. پشتهٔ ایمیل به اجرا ادامه میدهد و فایل وضعیت روی سرور دستنخورده میماند، پس بعداً از طریق اسکن نصب موجود میتوانید دوباره بپذیرید. برای پاک کردن ورودی کهنه یا اشتباهعلامتخورده استفاده کنید.",
+ "remove": "حذف",
+ "metaTitle": "فرادادهٔ نصب",
+ "metaServerId": "شناسهٔ سرور",
+ "metaPrimaryDomain": "دامنهٔ اصلی",
+ "metaStartedAt": "شروع در",
+ "metaFinishedAt": "پایان در",
+ "toolsTitle": "ابزارهای پشتهٔ ایمیل",
+ "toolsDesc": "اقدامات بازیابی که به پشتهٔ در حال اجرا دست میزنند بدون تغییر وضعیت. هر زمان امن است اجرا شود؛ جریان نامه چند ثانیه قطع میشود.",
+ "restartCardTitle": "راهاندازی مجدد پشتهٔ ایمیل",
+ "restartCardDesc": "هر دیمون ایمیل را یکجا چرخه میکند. سریعترین رفع وقتی چیزی بعد از استقرار خراب شد — ورود شکست میخورد، صف گیر میکند و غیره.",
+ "restartStack": "راهاندازی مجدد پشته",
+ "restartConfirmTitle": "پشتهٔ ایمیل راهاندازی مجدد شود؟",
+ "restartConfirmDesc": "هر دیمون ایمیل در حال اجرا را چرخه میکند (گیرندهٔ صف، سرویس IMAP، خط لولهٔ هرزنامه، fail2ban، و PostgreSQL). جریان نامه چند ثانیه تا برگشتن واحدها متوقف میشود. صندوقها، صفها و DNS دست نمیخورند.",
+ "restartSubmitting": "در حال راهاندازی مجدد…",
+ "restartBox": "وقتی چیزی درست بعد از استقرار یا تغییر پیکربندی عجیب به نظر میرسد استفاده کنید — بیشتر خرابیهای گذرا با یک چرخه پاک میشوند.",
+ "restartedToast": "{count} دیمون ایمیل راهاندازی مجدد شد.",
+ "restartedTitle": "پشتهٔ ایمیل راهاندازی مجدد شد",
+ "partialToast": "{failed} از {total} ناموفق: {units}",
+ "partialTitle": "راهاندازی مجدد جزئی",
+ "restartFailed": "راهاندازی مجدد ناموفق بود",
+ "restartFailedTitle": "راهاندازی مجدد پشتهٔ ایمیل ناموفق بود"
+ },
+ "test": {
+ "title": "آزمایش ارسال",
+ "description": "یک ایمیل آزمایشی واقعی از این سرور ایمیل بفرستید تا تحویل، DKIM و SPF را تأیید کنید.",
+ "sendButton": "ارسال ایمیل آزمایشی"
+ },
+ "reputation": {
+ "titlePrefix": "گرمکردن اعتبار برای ",
+ "body": "دامنههای ارسال کاملاً جدید هنوز اعتباری ندارند، پس ارائهدهندهها ممکن است نامهٔ اولیه را ۲۴ تا ۴۸ ساعت اول به هرزنامه بفرستند. این عادی است. پیامها را از صندوق آزمایشی «هرزنامه نیست» علامت بزنید و قابلیت تحویل سریع بهتر میشود. ",
+ "dismissLink": "میدانم، این را پنهان کن",
+ "dismiss": "نادیده گرفتن"
+ },
+ "engine": {
+ "downTitle": "موتور ایمیل روی این سرور اجرا نمیشود",
+ "downBody": "تا وقتی متوقف است هیچ نامهای تحویل یا پذیرفته نمیشود، و هر زبانه اینجا به آن نیاز دارد. روشن کردن دوباره امن است — چیزی دوباره نصب نمیشود و هیچ پیکربندی بازنویسی نمیشود.",
+ "legacyNote": "این سرور موتور قدیمی بومی میزبان (systemd Postfix/Dovecot) را اجرا میکند، پس کانتینری برای بازرسی نیست.",
+ "start": "شروع موتور",
+ "missingTitle": "موتور ایمیلی روی این سرور نیست",
+ "missingBody": "Openship اینجا موتور ایمیلی پیدا نمیکند — نه کانتینر موتور و نه واحدهای Postfix/Dovecot. برای نصب مجدد راهاندازی ایمیل را دوباره اجرا کنید.",
+ "rerunSetup": "اجرای دوبارهٔ راهاندازی ایمیل",
+ "cardBlocked": "تا اجرای موتور ایمیل در دسترس نیست."
+ },
+ "welcome": {
+ "invalidEmail": "یک آدرس ایمیل معتبر وارد کنید",
+ "sendFailed": "ارسال ناموفق بود",
+ "dismiss": "نادیده گرفتن",
+ "liveTitle": "سرور ایمیل شما زنده است.",
+ "liveDescAfter": " نامه میپذیرد. برای تأیید برای خودتان یک آزمایش بفرستید.",
+ "sendToLabel": "ارسال آزمایش به",
+ "skip": "رد کردن",
+ "sending": "در حال ارسال",
+ "sendTest": "ارسال آزمایش",
+ "sentTitle": "در راه است.",
+ "sentBefore": "آزمایشی از ",
+ "sentMiddle": " به ",
+ "sentAfter": " میرود. باید ظرف یک دقیقه برسد — صندوق ورودی و پوشهٔ هرزنامه را بررسی کنید.",
+ "gotIt": "متوجه شدم"
+ },
+ "restore": {
+ "titleMigrate": "مهاجرت به سرور دیگر",
+ "titleRestore": "بازیابی پشتیبان",
+ "startFailed": "نتوانستیم بازیابی را شروع کنیم",
+ "applyFailed": "نتوانستیم بازیابی را اعمال کنیم",
+ "reviewBefore": "بازیابی پشتیبان از ",
+ "reviewOntoFork": " روی سرور ایمیل دیگر.",
+ "reviewOntoInPlace": " روی این سرور ایمیل.",
+ "targetServer": "سرور مقصد",
+ "noTargetsBefore": "سرور ایمیل نصبشدهٔ دیگری نیست. ابتدا مقصد را با جادوگر نصب راهاندازی کنید (همان دامنه، ",
+ "noTargetsAfter": ")، سپس اینجا ظاهر میشود.",
+ "selectServer": "یک سرور انتخاب کنید…",
+ "warnMain": "این حسابها، دامنهها و نامهای مستعار سرور {which} را پاک میکند، سپس پشتیبان را بار میکند.",
+ "warnWhichTarget": "مقصد",
+ "warnWhichCurrent": "فعلی",
+ "warnForkExtra": "مقصد باید با همان دامنه راهاندازی شود. بعد از مهاجرت، MX/A DNS + PTR را به سرور جدید اشاره دهید.",
+ "confirmBefore": "برای تأیید ",
+ "confirmAfter": " را تایپ کنید",
+ "close": "بستن",
+ "cancel": "لغو",
+ "migrate": "مهاجرت",
+ "restore": "بازیابی",
+ "progressComplete": "بازیابی کامل شد",
+ "progressFailed": "بازیابی {status}",
+ "startingFallback": "در حال شروع",
+ "subDone": "{domain} بازیابی شد. دیمونهای ایمیل دوباره بارگذاری شدند.",
+ "subFailed": "خطا را بالا ببینید.",
+ "subRunning": "در حال انتقال حسابها، دامنهها و دادهٔ پیام. ممکن است طول بکشد."
+ },
+ "sendTest": {
+ "openGmail": "باز کردن Gmail",
+ "openOutlook": "باز کردن Outlook",
+ "openYahoo": "باز کردن Yahoo Mail",
+ "openIcloud": "باز کردن iCloud Mail",
+ "openProton": "باز کردن Proton Mail",
+ "openAol": "باز کردن AOL Mail",
+ "loadDomainsFailed": "بارگذاری دامنهها ناموفق بود",
+ "sentToast": "ایمیل آزمایشی ارسال شد. صندوق ورودی + هرزنامه را بررسی کنید.",
+ "toastTitle": "ایمیل آزمایشی",
+ "sendFailed": "ارسال ایمیل آزمایشی ناموفق بود",
+ "close": "بستن",
+ "title": "ارسال ایمیل آزمایشی",
+ "sendsFromBefore": "ارسال از ",
+ "sendsFromAfter": " — صندوق برای این دامنه در صورت نیاز فراهم میشود و با کلید دامنه DKIM امضا میشود.",
+ "sendToLabel": "ارسال به",
+ "fromDomainLabel": "دامنهٔ فرستنده",
+ "loadingDomains": "در حال بارگذاری دامنهها…",
+ "noActiveDomains": "دامنهٔ فعالی نیست",
+ "fromHint": "دامنهای را که بهعنوان آن احراز هویت میشود انتخاب کنید. برای تأیید دامنهٔ تازهافزودهشده بعد از انتشار رکوردهای DNS مفید است.",
+ "cancel": "لغو",
+ "sending": "در حال ارسال…",
+ "sendTest": "ارسال آزمایش",
+ "sentTitle": "ایمیل آزمایشی ارسال شد.",
+ "sentCheckBefore": "صندوق ورودی + هرزنامه را در ",
+ "sentCheckMiddle": " بررسی کنید. ارسال از ",
+ "sentCheckAfter": "."
+ },
+ "shared": {
+ "cancel": "لغو",
+ "actionFailed": "عمل ناموفق بود",
+ "logsTitle": "گزارشهای {label}",
+ "reload": "بارگذاری دوباره",
+ "close": "بستن",
+ "logsLoadFailed": "بارگذاری گزارشها ناموفق بود",
+ "loadingLogs": "در حال بارگذاری گزارشها…",
+ "noLogLines": "هنوز خط گزارشی برای این جزء نیست.",
+ "rowActions": "اقدامات برای {name}"
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/fa/importProject.json b/apps/dashboard/src/i18n/locales/fa/importProject.json
new file mode 100644
index 000000000..636857b29
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/fa/importProject.json
@@ -0,0 +1,434 @@
+{
+ "counts": {
+ "portOne": "{count} پورت",
+ "portOther": "{count} پورت",
+ "volumeOne": "{count} حجم",
+ "volumeOther": "{count} حجم",
+ "serviceOne": "{count} سرویس",
+ "serviceOther": "{count} سرویس",
+ "depOne": "{count} وابستگی",
+ "depOther": "{count} وابستگی",
+ "variableOne": "{count} متغیر",
+ "variableOther": "{count} متغیر"
+ },
+ "categories": {
+ "frontend": "فرانتاند",
+ "backend": "بکاند",
+ "fullstack": "کامل",
+ "static": "استاتیک",
+ "docker": "Docker",
+ "services": "سرویسها",
+ "generic": "عمومی"
+ },
+ "serviceStatus": {
+ "running": "در حال اجرا",
+ "built": "ساختهشده",
+ "building": "در حال ساخت",
+ "deploying": "در حال استقرار",
+ "failed": "ناموفق",
+ "pending": "در انتظار"
+ },
+ "buildSkeleton": {
+ "loading": "در حال بارگذاری نشست ساخت…"
+ },
+ "dockerSettings": {
+ "title": "Dockerfile تشخیص داده شد",
+ "subtitle": "پروژهٔ شما با Dockerfile خودش ساخته و استقرار میشود",
+ "infoCard": "تنظیمات ساخت لازم نیست — Dockerfile فرآیند ساخت را تعریف میکند. فقط پورتی که اپ به آن گوش میدهد را پیکربندی کنید.",
+ "exposedPort": "پورت در معرض",
+ "portHint": "پورتی که کانتینر در معرض میگذارد (از EXPOSE یا CMD)"
+ },
+ "buildSettings": {
+ "installCommandLabel": "دستور نصب",
+ "installCommandDesc": "دستور نصب وابستگیها",
+ "buildCommandLabel": "دستور ساخت",
+ "buildCommandDesc": "دستور ساخت پروژه",
+ "outputDirectoryLabel": "دایرکتوری خروجی",
+ "outputDirectoryDesc": "دایرکتوری حاوی خروجی ساخت",
+ "sourceFolderLabel": "پوشهٔ منبع",
+ "sourceFolderDesc": "از یک زیردایرکتوری داخل مخزن یا پروژهٔ محلی بسازید.",
+ "composePath": {
+ "title": "فایل Compose",
+ "subtitle": "بهصورت خودکار در ریشهٔ پروژه تشخیص داده میشود",
+ "label": "مسیر فایل Compose",
+ "description": "برای مخازنی که Compose را بیرون ریشه نگه میدارند — فایل را بدهید (deploy/stack.yml) یا پوشهای که آن را دارد (deploy/docker-compose). مخزن را دوباره اسکن میکند و سرویسهایی که اعلام کرده را استقرار میدهد.",
+ "apply": "اسکن",
+ "scanning": "در حال اسکن…",
+ "scanFailed": "نتوانستیم آن مسیر Compose را اسکن کنیم.",
+ "subtitleOptIn": "با Docker Compose استقرار میدهید؟ به فایل اشاره کنید."
+ },
+ "healthCheck": {
+ "title": "بررسیهای سلامت",
+ "subtitle": "خاموش — استقرار بهمحض اجرای اپ تمام میشود",
+ "subtitleOn": "روشن — استقرار قبل از زنده شدن منتظر اپ میماند",
+ "description": "Openship بعد از شروع اپ منتظرش نمیماند، پس بوت کند هرگز استقرار را تأخیر نمیاندازد یا ناموفق نمیکند. وقتی زنده شد، پورت بههرحال بررسی و گزارش میشود بدون اینکه چیزی را مسدود کند. اینها را فقط وقتی روشن کنید که خود استقرار نباید سبز شود.",
+ "probeLabel": "منتظر پاسخ اپ بمان",
+ "probeDesc": "بعد از شروع کانتینر، پورت اپ را شمارهگیری کن و منتظر پذیرش اتصال بمان.",
+ "pathLabel": "مسیر HTTP",
+ "pathPlaceholder": "/health",
+ "pathDesc": "لازم است GET اینجا زیر ۵۰۰ پاسخ دهد. خالی بگذارید تا هر اتصال TCP پذیرفته شود، که برای سرویسهای غیر HTTP هم کار میکند.",
+ "timeoutLabel": "انصراف بعد از (ثانیه)",
+ "stabilizationLabel": "مراقب حلقههای راهاندازی مجدد باش",
+ "stabilizationDesc": "کانتینر را بعد از شروع زیر نظر بگیر و حلقهٔ کرش را گزارش کن. زمان اجرا را میخواند، پس روی سرورهای راه دور هم کار میکند.",
+ "stabilizationWindowLabel": "نظارت بهمدت (ثانیه)",
+ "onFailureLabel": "اگر بررسی شکست خورد",
+ "onFailureWarn": "استقرار را زنده نگه دار و برای بازبینی علامت بزن",
+ "onFailureFail": "استقرار را ناموفق کن و نسخهٔ قبلی را در حال سرویسدهی نگه دار"
+ },
+ "buildImageLabel": "ایمیج ساخت",
+ "buildImageDesc": "ایمیج کانتینر سازنده برای ساخت ابری یا سروری. وقتی ایمیج پایهٔ تشخیصدادهشده غلط است آن را عوض کنید.",
+ "productionPathsLabel": "مسیرهای تولید",
+ "productionPathsDesc": "بعد از ساخت فقط این فایلها/پوشهها را استقرار بده — کد منبع را از زمان اجرا پنهان میکند. برای اجرا درجا خالی بگذارید.",
+ "startCommandLabel": "دستور شروع",
+ "startCommandDesc": "دستور شروع اپلیکیشن",
+ "productionPortDesc": "پورت تولید اپلیکیشن شما",
+ "productionPort": "پورت تولید",
+ "portForPrefix": "پورت برای",
+ "pathForPrefix": "مسیر برای",
+ "enterPort": "پورت را وارد کنید",
+ "optional": "(اختیاری)",
+ "save": "ذخیره",
+ "cancel": "لغو",
+ "deployConfig": "پیکربندی استقرار",
+ "defaultsApplied": "پیشفرضهای {framework} اعمال شد",
+ "configureOptions": "پیکربندی گزینههای ساخت",
+ "build": "ساخت",
+ "installBuildIn": "نصب و ساخت در {image}",
+ "installBuildCommands": "دستورات نصب و ساخت",
+ "deploySourceDirectly": "استقرار مستقیم منبع",
+ "advanced": "پیشرفته",
+ "start": "شروع",
+ "serverOnPort": "سرور روی پورت {port}",
+ "serverPortNotSet": "پورت سرور تنظیم نشده",
+ "staticFromEdge": "استاتیک از لبه",
+ "staticPathHint": "بهصورت فایل استاتیک از لبه سرو میشود — هیچ فرآیندی اجرا نمیشود.",
+ "modeServer": "سرور",
+ "modeWorker": "ورکر",
+ "modeStatic": "استاتیک",
+ "workerModeDesc": "مداوم اجرا میشود بدون پورت یا URL عمومی — مصرفکنندهٔ صف، cron، یا ربات.",
+ "buildSettingsTitle": "تنظیمات ساخت"
+ },
+ "projectSettings": {
+ "framework": "چارچوب",
+ "detected": "تشخیصدادهشده",
+ "change": "تغییر",
+ "useDetected": "استفاده از تشخیصدادهشده",
+ "selectHint": "چارچوب را انتخاب کنید تا تنظیمات ساخت خودکار پیکربندی شود"
+ },
+ "routingSection": {
+ "title": "مسیریابی",
+ "descDetected": "بازنویسیها، تغییر مسیرها و هدرها — روی یک دامنه سرو میشوند."
+ },
+ "environmentVariables": {
+ "title": "متغیرهای محیط",
+ "noneSet": "هیچکدام تنظیم نشده",
+ "pasteEnv": "چسباندن .env",
+ "uploadEnv": "آپلود .env",
+ "edit": "ویرایش",
+ "cancel": "لغو",
+ "saving": "در حال ذخیره…",
+ "saveChanges": "ذخیرهٔ تغییرات",
+ "collapse": "جمع کردن متغیرهای محیط",
+ "expand": "گسترش متغیرهای محیط",
+ "valuePlaceholder": "مقدار",
+ "dropHere": "فایل .env را اینجا رها کنید",
+ "noneTitle": "متغیر محیطی نیست",
+ "emptyHintEditing": "یک بلوک .env را هر جای این کادر بچسبانید، یا فایل .env را اینجا رها کنید.",
+ "emptyHintReadonly": "برای مدیریت متغیرهای محیط روی «ویرایش» کلیک کنید",
+ "pasteHint": "یک بلوک کامل .env را هر جای داخل این کادر بچسبانید.",
+ "addVariable": "افزودن متغیر",
+ "delete": "حذف",
+ "resolution": {
+ "needsValue": "نیاز به مقدار",
+ "fallbackDefault": "پیشفرض جایگزین",
+ "loadedFromEnv": "بارگذاریشده از .env",
+ "interpolated": "درونیابیشده"
+ },
+ "toast": {
+ "title": "متغیرهای محیط",
+ "added": "{count} اضافه شد",
+ "updated": "{count} بهروز شد",
+ "pastedOne": "{count} متغیر چسبانده شد{detail}",
+ "pastedOther": "{count} متغیر چسبانده شد{detail}",
+ "portSet": "پورت زمان اجرا از PORT روی {port} تنظیم شد",
+ "clipboardUnavailable": "دسترسی به کلیپبورد اینجا در دسترس نیست. داخل کادر محیط کلیک کنید و با Cmd/Ctrl+V بچسبانید.",
+ "clipboardEmpty": "کلیپبورد خالی است",
+ "clipboardInvalid": "کلیپبورد متغیرهای محیط معتبر KEY=VALUE ندارد",
+ "clipboardBlocked": "دسترسی به کلیپبورد مسدود شد. داخل کادر محیط کلیک کنید و با Cmd/Ctrl+V بچسبانید."
+ },
+ "reveal": {
+ "show": "نمایش مقادیر",
+ "hide": "پنهان کردن مقادیر",
+ "error": "نتوانستیم مقادیر را آشکار کنیم"
+ }
+ },
+ "composeServices": {
+ "domain": {
+ "internalService": "سرویس داخلی",
+ "noPublicPorts": "پورت عمومی تشخیص داده نشد",
+ "publicDomain": "دامنهٔ عمومی",
+ "internetEnabled": "ترافیک اینترنت فعال است",
+ "privateByDefault": "بهطور پیشفرض خصوصی",
+ "exposedPort": "پورت در معرض",
+ "portOption": "پورت {port}",
+ "domainLabel": "دامنه",
+ "free": "رایگان",
+ "custom": "سفارشی",
+ "routingTrafficPrefix": "مسیریابی ترافیک به پورت",
+ "duplicateTitle": "دامنهٔ تکراری",
+ "duplicateDescription": "دو مسیر به یک دامنه اشاره میکنند — یکی را عوض کنید تا هر مسیر دامنهٔ خودش را داشته باشد:"
+ },
+ "shared": {
+ "title": "محیط مشترک",
+ "rootEnvFound": "فایل .env ریشه پیدا شد",
+ "optionalVars": "متغیرهای اختیاری اعمالشده به هر سرویس",
+ "sharedVariableOne": "{count} متغیر مشترک",
+ "sharedVariableOther": "{count} متغیر مشترک",
+ "overrideNote": "· مقادیر سرویس بر مقادیر مشترک غلبه میکنند",
+ "importEnv": "ورود .env",
+ "manage": "مدیریت",
+ "modalSubtitle": "به هر سرویس اعمال میشود. در تعارض، متغیرهای سرویس برنده میشوند.",
+ "close": "بستن محیط مشترک",
+ "importFromRootOne": "ورود {count} متغیر از .env ریشه",
+ "importFromRootOther": "ورود {count} متغیر از .env ریشه"
+ },
+ "config": {
+ "title": "پیکربندی",
+ "summary": "{ports} · {volumes}",
+ "ports": "پورتها",
+ "containerLabel": "کانتینر",
+ "publishedLabel": "منتشرشده",
+ "equalsInsideService": "= داخل سرویس ·",
+ "equalsReachableHost": "= روی میزبان در دسترس.",
+ "cloudPortIgnored": "روی ابر پورت منتشرشده نادیده گرفته میشود — فقط پورت کانتینر مسیریابی میشود.",
+ "naOnCloud": "روی ابر صدق نمیکند",
+ "publicBadge": "عمومی",
+ "removePort": "حذف پورت",
+ "addPort": "افزودن پورت",
+ "volumes": "حجمها",
+ "statefulWarnPart1": "سرویس حالتدار روی ابر: داده روی دیسک پایدار فضای کار میماند (راهاندازی مجدد را زنده میماند) اما ",
+ "statefulWarnBold": "بین بازسازیها منتقل نمیشود",
+ "statefulWarnPart2": " مگر پشتیبان فعال باشد. اتصال حجم Docker اعمال نمیشود.",
+ "volumeHint": "حجم نامدار یا مسیر میزبان → مسیر کانتینر.",
+ "volumeCloudNote": "روی ابر این اتصالها اعمال نمیشوند — داده روی دیسک فضای کار میماند (بدون پشتیبان بین بازسازیها نه).",
+ "sourcePlaceholder": "منبع",
+ "containerPathPlaceholder": "مسیر کانتینر",
+ "readOnlyMount": "اتصال فقطخواندنی",
+ "removeVolume": "حذف حجم",
+ "addVolume": "افزودن حجم",
+ "command": "دستور",
+ "imageDefault": "(پیشفرض ایمیج)",
+ "restartPolicy": "سیاست راهاندازی مجدد",
+ "restartDefault": "پیشفرض (unless-stopped)"
+ },
+ "status": {
+ "public": "عمومی",
+ "private": "خصوصی",
+ "internal": "داخلی"
+ },
+ "card": {
+ "buildLabel": "ساخت: {value}",
+ "edit": "ویرایش",
+ "delete": "حذف",
+ "envVars": "متغیرهای محیط",
+ "noneConfigured": "چیزی پیکربندی نشده",
+ "variablesConfiguredOne": "{count} متغیر پیکربندی شده",
+ "variablesConfiguredOther": "{count} متغیر پیکربندی شده",
+ "missingSuffix": "· {count} ناقص",
+ "manage": "مدیریت",
+ "variableSuffixOne": "· {count} متغیر",
+ "variableSuffixOther": "· {count} متغیر",
+ "closeEnv": "بستن متغیرهای محیط",
+ "needsValueOne": "{count} متغیر محیط نیاز به مقدار دارد",
+ "needsValueOther": "{count} متغیر محیط نیاز به مقدار دارند"
+ },
+ "main": {
+ "deployingServices": "استقرار بهصورت سرویسها",
+ "deployingSingle": "استقرار بهصورت یک اپ واحد",
+ "buildCountSuffix": "· {count} ساخت",
+ "exposedCountSuffix": "· {count} در معرض",
+ "infoPart1": "سرویسهای داخلی با نام سرویس به هم میرسند. ",
+ "infoBold": "دامنهٔ عمومی",
+ "infoPart2": " را فقط برای سرویسهایی فعال کنید که باید ترافیک اینترنت بگیرند.",
+ "singleAppNote": "سرویسهای Compose تجزیهشده برای بعد نگه داشته میشوند، اما این استقرار از ساخت تکاپ معمولی، دستور شروع، محیط و تنظیمات دامنه استفاده میکند.",
+ "modeServicesLabel": "پشتهٔ سرویس",
+ "modeServicesDesc": "هر سرویس Compose را با زمان اجرا و دامنهٔ خودش استقرار بده.",
+ "modeSingleLabel": "اپ واحد",
+ "modeSingleDesc": "از جریان معمولی ساخت و دستور شروع برای یک اپ استفاده کن.",
+ "deploymentMode": "حالت استقرار",
+ "deploymentModeDesc": "{mode} · بین پشتهٔ سرویس و مدیریت اپ واحد جابهجا شوید."
+ }
+ },
+ "monorepo": {
+ "workspace": {
+ "title": "فضای کار",
+ "subtitle": "یک بار در ریشهٔ مخزن قبل از ساخت هر اپ اجرا میشود — نصب، codegen، همگامسازی اسکیما و غیره.",
+ "packageManager": "مدیر بسته",
+ "prepareCommand": "دستور آمادهسازی",
+ "chainPrefix": "زنجیره با",
+ "chainSuffix": "برای codegen، همگامسازی اسکیما و غیره."
+ },
+ "app": {
+ "collapse": "جمع کردن",
+ "expand": "گسترش",
+ "portSuffix": "· پورت {port}",
+ "deployAria": "استقرار این زیراپ",
+ "deploy": "استقرار"
+ },
+ "header": {
+ "title": "مونوریپو — {count} اپ تشخیص داده شد",
+ "singleDesc": "استقرار بهصورت اپ واحد — یک کانتینر، یک مجموعه دستور، همهٔ پورتها در معرض.",
+ "multiDesc": "{selected} از {total} انتخاب شده. هر اپ روی پورت و دامنهٔ خودش استقرار میشود و نصب فضای کار را در ریشهٔ مخزن به اشتراک میگذارد."
+ },
+ "singleNote": "تعاریف زیراپ برای بعد نگه داشته میشوند. این استقرار از دستورات ساخت و شروع تکاپ معمولی زیر استفاده میکند — پورت هر زیراپ فعال از همان زمان اجرا در معرض است.",
+ "modes": {
+ "servicesLabel": "زمان اجرای بهازای اپ",
+ "servicesDesc": "هر زیراپ را با زمان اجرا، پورت و دامنهٔ خودش استقرار بده.",
+ "singleLabel": "اپ واحد",
+ "singleDesc": "ساخت + دستور شروع یک زیراپ را ارتقا بده، پورت همهٔ اپها را از یک زمان اجرا در معرض بگذار."
+ },
+ "deploymentMode": "حالت استقرار",
+ "deploymentModeDesc": "{mode} · بین زمان اجرای بهازای اپ و مدیریت اپ واحد جابهجا شوید."
+ },
+ "deploymentProcessing": {
+ "targetOpenshipCloud": "Openship Cloud",
+ "targetServerNamed": "سرور · {name}",
+ "targetServer": "سرور",
+ "targetLocal": "ماشین محلی",
+ "strategyLocal": "ساخت محلی",
+ "strategyCloud": "ساخت ابری",
+ "strategyServer": "ساخت سرور",
+ "strategyHost": "ساخت میزبان",
+ "promptDetails": {
+ "port": "پورت",
+ "process": "فرآیند",
+ "unitDescription": "توضیح واحد",
+ "openshipDeployment": "استقرار Openship",
+ "sites": {
+ "lead": "{count} سایت الان توسط پروکسی موجود سرو میشوند. مهاجرت آنها را قبل از تحویل گرفتن پورتهای ۸۰/۴۴۳ به Openship وارد میکند:",
+ "tlsBadge": "TLS",
+ "staticLabel": "استاتیک:",
+ "warningsTitle": "نتوانست بهصورت خودکار مهاجرت شود — اینها را دستی دوباره اضافه کنید:"
+ }
+ },
+ "portAdvisory": {
+ "title": "این پورت درست است؟",
+ "bannerTitle": "ممکن است پورتی اشتباه پیکربندی شده باشد",
+ "review": "بازبینی",
+ "note": "مسیریابی به این پورت اشاره میکند — اگر غلط باشد، درخواستها با ۵۰۲ شکست میخورند.",
+ "body": "روی پورت {port} کسی پاسخ نداد. اگر اپ روی پورت دیگری گوش میدهد، پورت درست را اینجا تنظیم کنید.",
+ "bodyService": "سرویس «{service}»: روی پورت {port} کسی پاسخ نداد. اگر روی پورت دیگری گوش میدهد، پورت درست را اینجا تنظیم کنید.",
+ "newPortLabel": "پورت درست",
+ "changePort": "تغییر پورت",
+ "skip": "رد کردن",
+ "applied": "مسیریابی به پورت {port} بهروز شد.",
+ "changeError": "نتوانستیم پورت را بهروز کنیم. دوباره تلاش کنید."
+ },
+ "title": {
+ "cancelled": "استقرار لغو شد",
+ "failed": "استقرار ناموفق بود",
+ "readyWarnings": "استقرار آماده با هشدار",
+ "successful": "استقرار موفق",
+ "deploying": "در حال استقرار…"
+ },
+ "visitSite": "بازدید از سایت",
+ "warningTitle": "استقرار تمام شد، اما بعضی سرویسها هنوز توجه میخواهند.",
+ "productionLogs": "گزارش تولید",
+ "buildTerminal": "ترمینال ساخت",
+ "seeLogs": "برای جزئیات مشکل گزارشها را ببینید",
+ "redeploying": "در حال استقرار مجدد…",
+ "stopping": "در حال توقف…",
+ "stopDeployment": "توقف استقرار",
+ "redeploy": "استقرار مجدد",
+ "goToProject": "رفتن به پروژه",
+ "openDashboard": "باز کردن داشبورد",
+ "edit": "ویرایش",
+ "status": {
+ "cancelled": "لغوشده",
+ "failed": "ناموفق",
+ "readyWarnings": "آماده با هشدار",
+ "ready": "آماده",
+ "building": "در حال ساخت"
+ },
+ "detailsTitle": "جزئیات استقرار",
+ "detailStatus": "وضعیت",
+ "detailInstance": "نمونه",
+ "detailBuild": "ساخت",
+ "detailBuildTime": "زمان ساخت",
+ "detailFramework": "چارچوب",
+ "detailBranch": "شاخه",
+ "detailDomains": "دامنهها",
+ "detailDomain": "دامنه"
+ },
+ "composeDeployment": {
+ "title": {
+ "cancelled": "استقرار لغو شد",
+ "failed": "استقرار ناموفق بود",
+ "actionRequired": "اقدام لازم است",
+ "warnings": "استقرار با هشدار",
+ "successful": "استقرار موفق",
+ "deploying": "در حال استقرار سرویسها…"
+ },
+ "decisionBannerTitle": "اقدام لازم است — بعضی سرویسها شکست خوردند",
+ "decisionBannerDefaultMsg": "این استقرار با سرویسهای ناموفق تمام شد. نگه دارید و بعداً درست کنید، یا رد کنید تا نسخهٔ قبلی بازیابی شود.",
+ "review": "بازبینی",
+ "warningTitle": "بعضی سرویسها توجه میخواهند",
+ "redeploying": "در حال استقرار مجدد…",
+ "stopping": "در حال توقف…",
+ "stopDeployment": "توقف استقرار",
+ "redeploy": "استقرار مجدد",
+ "openDashboard": "باز کردن داشبورد",
+ "editConfiguration": "ویرایش پیکربندی",
+ "logsTitle": "گزارش استقرار",
+ "logsSubtitle": "خروجی زنده، استریمشده بهازای هر سرویس",
+ "prepareTab": "آمادهسازی",
+ "noLogsFor": "برای {service} گزارشی ثبت نشد.",
+ "waitingFor": "در انتظار گزارش {service}…",
+ "noPrepareLogs": "گزارش آمادهسازی ثبت نشد.",
+ "preparingContext": "در حال کلون منبع و آمادهسازی زمینهٔ ساخت…",
+ "preparingServiceLogs": "در حال آمادهسازی گزارش سرویس…",
+ "toast": {
+ "keptMsg": "استقرار نگه داشته شد",
+ "keptTitle": "نگهداشتهشده",
+ "rejectedMsg": "استقرار جزئی رد شد",
+ "rejectedTitle": "استقرار برگشت داده شد",
+ "retryFailMsg": "نتوانستیم تلاش دوباره را شروع کنیم",
+ "retryTitle": "تلاش دوباره"
+ },
+ "partial": {
+ "title": "استقرار با سرویسهای ناموفق تمام شد",
+ "body": "{failed} از {total} سرویس شکست خورد، اما بقیهٔ پشته با موفقیت استقرار شد. میتوانید این استقرار را نگه دارید و بعداً سرویسهای ناموفق را درست کنید، یا رد کنید و استقرار قبلی را بازیابی کنید.",
+ "warning": "هشدار",
+ "rejectNote": "رد کردن کانتینرهای این استقرار جزئی را پایین میآورد و اگر استقرار قبلی وجود داشته باشد آن را بازیابی میکند. رکورد استقرار و گزارش ساخت در تاریخچه میمانند تا ببینید چه شکست خورد.",
+ "keep": "نگه دار و بعداً درست کن",
+ "rejecting": "در حال رد…",
+ "reject": "رد استقرار",
+ "retrying": "در حال تلاش دوباره…",
+ "retryOne": "تلاش دوباره برای {count} سرویس ناموفق",
+ "retryOther": "تلاش دوباره برای {count} سرویس ناموفق"
+ }
+ },
+ "composeServiceTally": {
+ "running": "در حال اجرا",
+ "builtSuffix": "· {count} ساختهشده",
+ "buildingSuffix": "· {count} در حال ساخت",
+ "failedSuffix": "· {count} ناموفق"
+ },
+ "composeSidebar": {
+ "status": {
+ "cancelled": "لغوشده",
+ "failed": "ناموفق",
+ "warnings": "هشدارها",
+ "ready": "آماده",
+ "deploying": "در حال استقرار"
+ },
+ "detailsTitle": "جزئیات استقرار",
+ "rowStatus": "وضعیت",
+ "rowTarget": "مقصد",
+ "rowBuild": "ساخت",
+ "rowBuildTime": "زمان ساخت",
+ "rowServices": "سرویسها",
+ "rowProject": "پروژه",
+ "rowType": "نوع"
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/fa/issues.json b/apps/dashboard/src/i18n/locales/fa/issues.json
new file mode 100644
index 000000000..c52b7c21e
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/fa/issues.json
@@ -0,0 +1,92 @@
+{
+ "title": "مشکلات",
+ "subtitle": "هر چیزی که Openship در پروژهها، سرورها و دامنههای شما نادرست یافته است.",
+ "tabs": {
+ "open": "باز",
+ "resolved": "حلشده"
+ },
+ "resolvedNote": "تاریخچه شامل رویدادهای کانتینر و سرور است — سایر بررسیها وضعیت فعلی را گزارش میکنند، بنابراین گواهی یا استقرار رفعشده دیگر اینجا دیده نمیشود.",
+ "rescan": "اسکن دوباره",
+ "rescanning": "در حال اسکن",
+ "filters": {
+ "searchPlaceholder": "جستجوی مشکلات",
+ "all": "همه",
+ "outage": "قطعی",
+ "action_required": "نیاز به اقدام",
+ "advisory": "توصیه",
+ "clear": "پاک کردن فیلترها"
+ },
+ "scopes": {
+ "platform": "Openship",
+ "server": "سرورها",
+ "project": "پروژهها",
+ "domain": "دامنهها"
+ },
+ "scopeSubtitles": {
+ "platform": "خود این نصب",
+ "server": "ماشینهایی که بار کاری روی آنها اجرا میشود",
+ "project": "پروژههای مستقر و کانتینرهایشان",
+ "domain": "نامهای میزبان سفارشی و گواهیها"
+ },
+ "kinds": {
+ "deploy_blocked": "استقرار مسدود شده",
+ "prompt": "در انتظار تصمیم",
+ "partial_decision": "انتشار جزئی",
+ "routing_unsynced": "مسیریابی همگام نیست",
+ "domain_unverified": "دامنه تأیید نشده",
+ "ssl_error": "مشکل گواهی",
+ "port_advisory": "توصیه پورت",
+ "workload_unhealthy": "بررسی سلامت ناموفق",
+ "workload_crash_loop": "کرش تکراری",
+ "workload_down": "کانتینر خاموش",
+ "server_unreachable": "سرور در دسترس نیست",
+ "edge_down": "Edge قطع است",
+ "edge_absent": "Edge نصب نشده",
+ "mail_down": "موتور ایمیل قطع است",
+ "update_available": "بهروزرسانی موجود است",
+ "component_behind": "بهروزرسانی مؤلفه موجود است"
+ },
+ "since": "شروع از {when}",
+ "resolvedWhen": "حلشده {when}",
+ "expiresIn": "تا {n} دقیقه دیگر لغو میشود",
+ "view": "مشاهده",
+ "fix": "رفع",
+ "install": "نصب",
+ "update": "بهروزرسانی",
+ "mailSetup": "راهاندازی ایمیل",
+ "selfUpdateNote": "این نصب خودش را از خط فرمان بهروز میکند.",
+ "empty": {
+ "openTitle": "چیزی نیاز به توجه ندارد",
+ "openBody": "هیچ استقراری مسدود یا در انتظار نیست، مشکل گواهی یا دامنه نیست، چیزی عقب نمانده و پورتی اشتباه نیست. بررسیها در پسزمینه ادامه دارند.",
+ "resolvedTitle": "رویداد حلشدهای نیست",
+ "resolvedBody": "رویدادهای کانتینر و سرور پس از بازیابی اینجا ظاهر میشوند و ۳۰ روز نگه داشته میشوند.",
+ "filteredTitle": "مطابقتی نیست",
+ "filteredBody": "برخی مشکلات باز هستند — هیچکدام با این فیلتر مطابقت ندارند."
+ },
+ "summary": {
+ "title": "نمای کلی",
+ "subtitle": "سلامت در یک نگاه",
+ "totalOpen": "باز",
+ "totalResolved": "حلشده",
+ "bySeverity": "بر اساس شدت",
+ "byArea": "بر اساس حوزه"
+ },
+ "home": {
+ "attentionTitle": "نیاز به توجه",
+ "attentionSubtitle": "چیزی خراب است یا منتظر شماست",
+ "updatesTitle": "بهروزرسانی موجود است",
+ "updatesSubtitle": "نسخههای جدید آماده اعمال",
+ "more": "{n} مورد دیگر",
+ "hide": "پنهان کن تا تغییری رخ دهد"
+ },
+ "loadFailed": "بارگذاری مشکلات ممکن نشد",
+ "toast": {
+ "title": "مشکلات",
+ "rescanned": "اسکن دوباره تمام شد",
+ "rescanPartial": "اسکن دوباره تمام شد، {n} بررسی ناموفق بود",
+ "rescanFailed": "اسکن دوباره ناموفق بود",
+ "resolved": "انجام شد",
+ "resolveFailed": "اعمال آن رفع ممکن نشد",
+ "confirmDestructive": "این کار اثر جانبی غیرقابلبازگشتی دارد. ادامه میدهید؟"
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/fa/jobs.json b/apps/dashboard/src/i18n/locales/fa/jobs.json
new file mode 100644
index 000000000..158de9b61
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/fa/jobs.json
@@ -0,0 +1,186 @@
+{
+ "backups": {
+ "section": "زمانبندی پشتیبان",
+ "sectionDesc": "پشتیبانهای خودکار زمانبندیشده برای هر پروژه یا سرویس. از زبانهٔ پشتیبان هر پروژه مدیریت میشود.",
+ "projectDefault": "پیشفرض پروژه",
+ "mailServer": "سرور ایمیل",
+ "manage": "مدیریت در پشتیبانها",
+ "badge": "پشتیبان",
+ "never": "هرگز اجرا نشده",
+ "status": {
+ "succeeded": "موفق",
+ "failed": "ناموفق",
+ "cancelled": "لغو شده",
+ "running": "در حال اجرا"
+ }
+ },
+ "title": "کارها",
+ "subtitle": "نگهداری داخلی و دستورهای سفارشی که روی سرورهای شما اجرا میشوند.",
+ "empty": "هنوز کار زمانبندیشدهای نیست.",
+ "searchPlaceholder": "جستجوی کارها…",
+ "noResults": "هیچ کاری با فیلترهای شما همخوان نیست.",
+ "overview": {
+ "title": "در یک نگاه",
+ "subtitle": "کارهای این نمونه",
+ "all": "همهٔ کارها",
+ "running": "در حال اجرا",
+ "failed": "ناموفق",
+ "scheduled": "زمانبندیشده",
+ "disabled": "غیرفعال"
+ },
+ "loadFailed": "بارگذاری کارها ناموفق بود.",
+ "kind": {
+ "system": "سیستم",
+ "custom": "سفارشی"
+ },
+ "fields": {
+ "schedule": "زمانبندی",
+ "nextRun": "اجرای بعدی",
+ "lastRun": "آخرین اجرا",
+ "never": "هرگز اجرا نشده",
+ "notScheduled": "زمانبندی نشده"
+ },
+ "status": {
+ "success": "موفق",
+ "failed": "ناموفق",
+ "running": "در حال اجرا"
+ },
+ "actions": {
+ "run": "الان اجرا کن",
+ "running": "در حال اجرا…",
+ "edit": "ویرایش زمانبندی",
+ "save": "ذخیره",
+ "cancel": "لغو",
+ "enable": "فعال کردن",
+ "disable": "غیرفعال کردن",
+ "viewLogs": "لاگها"
+ },
+ "cronHint": "cron استاندارد ۵ فیلدی (دقیقه ساعت روز ماه روزهفته)، UTC.",
+ "toast": {
+ "ran": "{label} شروع شد.",
+ "ranFailed": "اجرای {label} ناموفق بود.",
+ "saved": "زمانبندی {label} بهروز شد.",
+ "saved2": "کار بهروز شد.",
+ "saveFailed": "بهروزرسانی {label} ناموفق بود.",
+ "enabled": "{label} فعال شد.",
+ "disabled": "{label} غیرفعال شد.",
+ "toggleFailed": "بهروزرسانی {label} ناموفق بود.",
+ "title": "کارها",
+ "created": "کار ایجاد شد.",
+ "createFailed": "ایجاد کار ناموفق بود.",
+ "deleted": "کار حذف شد.",
+ "deleteFailed": "حذف کار ناموفق بود."
+ },
+ "newJob": "کار جدید",
+ "sections": {
+ "system": "کارهای سیستم",
+ "systemDesc": "نگهداری داخلی روی این نمونه.",
+ "custom": "کارهای سفارشی",
+ "customDesc": "دستورهایی که طبق زمانبندی روی سرورهایتان اجرا میکنید."
+ },
+ "customEmpty": {
+ "title": "هنوز کار سفارشی نیست",
+ "desc": "دستوری را روی یکی از سرورها اجرا کنید — زمانبندیشده، یکبار، یا با رویداد — با تلاش دوباره، سکرت، لاگ زنده و اعلان.",
+ "cta": "ایجاد کار",
+ "docs": "مستندات"
+ },
+ "create": {
+ "title": "کار جدید",
+ "name": "نام",
+ "namePlaceholder": "vacuum شبانهٔ DB",
+ "server": "سرور",
+ "servers": "سرورها",
+ "noServers": "هنوز سروری وصل نیست — اول یکی اضافه کنید.",
+ "command": "دستور",
+ "commandPlaceholder": "pg_dump ... > /backups/db.sql",
+ "commandHint": "از طریق SSH روی سرور(های) انتخابشده اجرا میشود. یک `docker run --rm image cmd` هم کار میکند.",
+ "scheduleType": "زمان اجرا",
+ "scheduleTypes": {
+ "recurring": "طبق زمانبندی",
+ "once": "یکبار",
+ "manual": "دستی"
+ },
+ "sections": {
+ "basics": "اصول",
+ "schedule": "زمانبندی",
+ "servers": "سرورهای مقصد",
+ "reliability": "قابلیت اطمینان",
+ "environment": "محیط",
+ "triggers": "وابستگیها و تریگرها",
+ "notifications": "اعلانها"
+ },
+ "schedule": "زمانبندی (cron)",
+ "runAt": "اجرا در",
+ "manualHint": "فقط وقتی تریگر شود اجرا میشود — اجرای الان، یک وابستگی، یا یک رویداد.",
+ "timeout": "مهلت (ثانیه)",
+ "retryAttempts": "حداکثر تلاش",
+ "retryBackoff": "تأخیر بین تلاش (ثانیه)",
+ "advanced": "پیشرفته",
+ "env": "متغیرهای محیطی",
+ "addVar": "افزودن متغیر",
+ "secrets": "سکرتها",
+ "addSecret": "افزودن سکرت",
+ "replaceSecrets": "جایگزینی سکرتها",
+ "secretsSet": "سکرت تنظیمشده",
+ "noSecrets": "سکرتی تنظیم نشده.",
+ "dependencies": "اجرا پس از",
+ "dependenciesHint": "این کار پس از موفقیت کار(های) انتخابشده اجرا میشود.",
+ "triggers": "تریگرهای رویداد",
+ "notifications": "اعلانها",
+ "noChannels": "هنوز کانال اعلانی نیست — یکی در تنظیمات ← اعلانها اضافه کنید.",
+ "notificationsHint": "تنظیمات سراسری اعلان را برای این کار بازنویسی میکند.",
+ "submit": "ایجاد کار",
+ "creating": "در حال ذخیره…",
+ "cancel": "لغو",
+ "needServer": "اول یک سرور وصل کنید",
+ "pasteEnv": "چسباندن .env",
+ "uploadEnv": "آپلود .env",
+ "envPasteEmpty": "متغیری پیدا نشد",
+ "comingSoon": "بهزودی",
+ "setupChannel": "راهاندازی کانال",
+ "customTriggerSoon": "تریگر سفارشی",
+ "docsLink": "دربارهٔ کارها بیشتر بدانید",
+ "docsBody": "زمانبندی، تلاش دوباره، سکرت و اعلان",
+ "summary": {
+ "title": "خلاصه",
+ "command": "دستور",
+ "schedule": "زمانبندی",
+ "targets": "مقصدها",
+ "retry": "تلاش دوباره",
+ "timeout": "مهلت",
+ "notify": "اعلانها",
+ "none": "—",
+ "retryOff": "هیچ"
+ }
+ },
+ "edit": {
+ "title": "ویرایش کار",
+ "action": "ویرایش",
+ "save": "ذخیرهٔ تغییرات"
+ },
+ "detail": {
+ "back": "بازگشت به کارها",
+ "tabs": {
+ "overview": "نمای کلی",
+ "runs": "اجراها"
+ },
+ "servers": "سرورها",
+ "retry": "تلاش دوباره",
+ "retryValue": "{n} تلاش، {s}ث تأخیر",
+ "notifyValue": "{c} کانال در {s}",
+ "onceAt": "یکبار در",
+ "noRuns": "هنوز اجرایی نیست."
+ },
+ "delete": {
+ "action": "حذف",
+ "confirm": "این کار حذف شود؟",
+ "deleting": "در حال حذف…"
+ },
+ "logs": {
+ "title": "لاگهای اجرا",
+ "streaming": "در حال استریم…",
+ "empty": "هنوز خروجی نیست.",
+ "close": "بستن",
+ "exit": "کد خروج {code}"
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/fa/library.json b/apps/dashboard/src/i18n/locales/fa/library.json
new file mode 100644
index 000000000..6cecba3e1
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/fa/library.json
@@ -0,0 +1,187 @@
+{
+ "ghCliConsent": {
+ "title": "مخزنهای شما خوانده شود؟",
+ "body": "Openship میتواند مخزنهای GitHub شما را با gh CLI که از قبل وارد آن شدهاید فهرست کند.",
+ "bodyWithLogin": "Openship میتواند مخزنهای GitHub شما را با gh CLI که بهعنوان @{login} وارد آن شدهاید فهرست کند.",
+ "pointNoCloud": "چیزی به ابر فرستاده نمیشود — فهرست هرگز این ماشین را ترک نمیکند.",
+ "pointDesktopOnly": "فقط در اپ دسکتاپ اجرا میشود، با احراز هویت محلی gh.",
+ "pointReadOnly": "فقط خواندنی: مخزنها را فهرست میکند تا یکی را برای استقرار انتخاب کنید.",
+ "allow": "اجازه دادن"
+ },
+ "page": {
+ "title": "پروژه جدید",
+ "subtitle": "یک مخزن وارد کنید، URL جایگذاری کنید، یا از قالب شروع کنید",
+ "tabs": {
+ "folder": "پوشه",
+ "github": "GitHub",
+ "url": "نشانی Git",
+ "template": "قالب"
+ }
+ },
+ "connect": {
+ "connectGithub": "اتصال GitHub",
+ "manageInSettings": "سایر روشهای اتصال",
+ "terminal": {
+ "title": "اتصال GitHub",
+ "checkConnection": "بررسی اتصال",
+ "afterLogin": "پس از ورود، برای ادامه روی «بررسی اتصال» کلیک کنید."
+ },
+ "deviceFlow": {
+ "title": "ورود به GitHub",
+ "goToPrefix": "بروید به",
+ "goToSuffix": "این کد را در GitHub وارد کنید تا ورود تمام شود.",
+ "waiting": "در انتظار مجوز…",
+ "openGithub": "باز کردن GitHub",
+ "copyCode": "برای کپی کلیک کنید"
+ },
+ "default": {
+ "title": "برای استقرار به GitHub متصل شوید",
+ "descSelfHosted": "نحوه دسترسی Openship به مخزنهایتان را انتخاب کنید.",
+ "descSaas": "حساب GitHub را پیوند دهید تا مخزنها را ببینید و با هر push مستقر شوید.",
+ "recommended": "پیشنهادی",
+ "cloudApp": "GitHub App ابری Openship",
+ "cloudAppDesc": "GitHub App مدیریتشده — استقرار روی سرورهای راه دور با توکنهای کوتاهعمر.",
+ "connectCloud": "اتصال Openship Cloud",
+ "manual": "اتصال دستی GitHub",
+ "manualDesc": "در تنظیمات راهاندازی کنید — GitHub App/OAuth خودتان، یا gh CLI همین نمونه. بدون حساب Openship.",
+ "manualCta": "راهاندازی در تنظیمات",
+ "localBuilds": "ساختهای محلی",
+ "ghCli": "ورود با GitHub",
+ "ghCliDesc": "ورود مرورگر با کد دستگاه. بدون راهاندازی اپ، بدون SSH، بدون حساب Openship.",
+ "useGhCli": "ورود",
+ "connecting": "در حال اتصال…",
+ "byoc": "کلید SSH یا توکن دسترسی",
+ "byocDesc": "اعتبار خودتان را بیاورید — یک کلید استقرار برای هر سرور، یا یک توکن. بدون ورود.",
+ "byocCta": "راهاندازی"
+ },
+ "token": {
+ "title": "اتصال با توکن GitHub",
+ "connect": "اتصال",
+ "create": "ساخت توکن در GitHub",
+ "failed": "ذخیره توکن ممکن نشد."
+ }
+ },
+ "folderUpload": {
+ "emptyFolder": "آن پوشه خالی به نظر میرسد (یا همه چیز نادیده گرفته شده). یک پوشه پروژه انتخاب کنید.",
+ "uploadError": "بارگذاری پوشه ناموفق بود",
+ "stackTitle": "چه چیزی را مستقر میکنید؟",
+ "stackDesc": "استک پوشه را انتخاب کنید — این محیط ساخت را تعیین میکند. سپس بارگذاری میکنید.",
+ "backToStack": "بازگشت به انتخاب استک",
+ "uploadTitle": "پوشه {stack} خود را بارگذاری کنید",
+ "uploadSubtitle": "در مرورگر بستهبندی میشود — node_modules، .git و خروجی ساخت رد میشوند",
+ "dropTitle": "پوشه پروژه را رها کنید یا برای مرور کلیک کنید",
+ "dropSubtitle": "کل پوشه بارگذاری و بهعنوان اپ {stack} ساخته میشود",
+ "filesSingular": "{count} پرونده",
+ "filesPlural": "{count} پرونده",
+ "clearSelection": "پاک کردن انتخاب",
+ "packing": "در حال بستهبندی پوشه…",
+ "uploading": "در حال بارگذاری…",
+ "continue": "ادامه"
+ },
+ "localProjects": {
+ "title": "پروژههای محلی",
+ "loading": "در حال بارگذاری...",
+ "projectCountSingular": "{count} پروژه",
+ "projectCountPlural": "{count} پروژه",
+ "import": "وارد کردن",
+ "empty": {
+ "title": "هنوز پروژه محلی نیست",
+ "description": "یک پوشه پروژه را از ماشین خود وارد کنید تا بهصورت محلی اسکن، ساخته و مستقر شود",
+ "importProject": "وارد کردن پروژه",
+ "howItWorks": "چگونه کار میکند",
+ "dropTitle": "رها کنید یا مرور کنید",
+ "dropDesc": "هر پوشه پروژهای را انتخاب کنید",
+ "autoDetectTitle": "تشخیص خودکار",
+ "autoDetectDesc": "فریمورک و پیکربندی پیدا شد",
+ "deployTitle": "استقرار محلی",
+ "deployDesc": "روی ماشین خود بسازید و اجرا کنید"
+ },
+ "form": {
+ "title": "وارد کردن از پوشه",
+ "scanErrorDefault": "اسکن پوشه ناموفق بود",
+ "dropTitle": "یک پوشه را اینجا رها کنید یا برای مرور کلیک کنید",
+ "dropSubtitle": "پوشه پروژه را انتخاب کنید تا فریمورک بهصورت خودکار تشخیص داده شود",
+ "folderViaBrowser": "پوشه از طریق مرورگر انتخاب شد — مسیر مطلق کامل را وارد کنید و اسکن را بزنید",
+ "folderDropped": "پوشه رها شد — مسیر مطلق کامل را وارد کنید و اسکن را بزنید",
+ "pathPlaceholder": "/path/to/your/project",
+ "browse": "مرور",
+ "scan": "اسکن",
+ "importError": "وارد کردن پروژه ناموفق بود",
+ "projectName": "نام پروژه",
+ "detected": "تشخیصدادهشده",
+ "build": "ساخت:",
+ "install": "نصب:",
+ "output": "خروجی:",
+ "start": "شروع:",
+ "importing": "در حال وارد کردن...",
+ "importProject": "وارد کردن پروژه"
+ }
+ },
+ "repositoryList": {
+ "searchPlaceholder": "جستجوی مخزنها...",
+ "visibility": {
+ "all": "همه",
+ "public": "عمومی",
+ "private": "خصوصی"
+ },
+ "sort": {
+ "recent": "اخیر",
+ "name": "نام",
+ "stars": "ستاره"
+ },
+ "addAccount": "افزودن حساب GitHub",
+ "installTitle": "نصب GitHub App",
+ "installDesc": "GitHub App را روی حساب یا سازمان خود نصب کنید تا به مخزنها دسترسی داده شود.",
+ "installButton": "نصب GitHub App",
+ "noMatching": "مخزن مطابقی نیست",
+ "noRepos": "مخزنی پیدا نشد",
+ "noMatchingDesc": "عبارت جستجوی دیگری امتحان کنید یا فیلترها را تنظیم کنید",
+ "noReposDesc": "این حساب هنوز مخزنی ندارد",
+ "privateBadge": "خصوصی",
+ "localOnly": "فقط محلی",
+ "localOnlyTooltip": "اپ Openship روی {owner} نصب نشده. فقط ساخت محلی — برای استقرار راه دور، App را روی این مالک نصب کنید.",
+ "repoCountSingular": "{count} مخزن",
+ "repoCountPlural": "{count} مخزن",
+ "matching": "مطابق «{query}»",
+ "pagination": {
+ "previous": "قبلی",
+ "next": "بعدی",
+ "pageOf": "صفحه {page} از {total}"
+ },
+ "time": {
+ "justNow": "همین الان",
+ "minutesAgo": "{n} دقیقه پیش",
+ "hoursAgo": "{n} ساعت پیش",
+ "daysAgo": "{n} روز پیش",
+ "monthsAgo": "{n} ماه پیش"
+ }
+ },
+ "sidebar": {
+ "overview": "نمای کلی",
+ "total": "کل",
+ "public": "عمومی",
+ "private": "خصوصی",
+ "quickTip": "نکته سریع",
+ "quickTipDesc": "هر مخزنی را انتخاب کنید تا فوری مستقر شود. استقرار خودکار را روی هر push پیکربندی کنید.",
+ "connection": "اتصال",
+ "openshipGithubApp": "اپ GitHub مربوط به Openship",
+ "connected": "متصل",
+ "notConnected": "متصل نیست",
+ "ghCli": "gh CLI",
+ "runGhAuth": "اجرای `gh auth login`",
+ "openshipCloudApp": "اپ ابری Openship",
+ "checking": "در حال بررسی…",
+ "notConnectedManage": "متصل نیست — مدیریت در تنظیمات",
+ "optional": "اختیاری",
+ "footnote": "کتابخانه محلی از طریق gh CLI؛ استقرارهای راه دور از توکنهای کوتاهعمر استفاده میکنند —",
+ "manageGithubSettings": "مدیریت GitHub در تنظیمات",
+ "methodToken": "توکن GitHub",
+ "methodDevice": "ورود GitHub"
+ },
+ "urlImport": {
+ "invalidUrl": "یک URL معتبر مخزن GitHub وارد کنید",
+ "title": "وارد کردن از Git URL",
+ "description": "URL یک مخزن عمومی را جایگذاری کنید. اتصال GitHub لازم نیست.",
+ "importButton": "وارد کردن مخزن"
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/fa/migration.json b/apps/dashboard/src/i18n/locales/fa/migration.json
new file mode 100644
index 000000000..74784db9d
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/fa/migration.json
@@ -0,0 +1,261 @@
+{
+ "wizard": {
+ "title": "مهاجرت اپها",
+ "intro": "اپهای در حال اجرای این سرور را بررسی کنید و آنها را به Openship بیاورید. فقط خواندنی است — تا وقتی استقرار نکنید چیزی روی سرور عوض نمیشود.",
+ "selectServer": "یک سرور انتخاب کنید",
+ "scan": "اسکن سرور",
+ "scanning": "در حال اسکن…",
+ "rescan": "اسکن دوباره",
+ "flatDocker": "فهرست تخت",
+ "flatDockerHint": "برچسبهای Openship را نادیده بگیر — همه چیز (از جمله کانتینرهای مدیریتشدهٔ Openship) را بهعنوان Docker ساده بپذیر.",
+ "flatDockerShort": "کانتینرهای مدیریتشدهٔ Openship را هم بپذیر.",
+ "cancel": "لغو",
+ "close": "بستن",
+ "projectName": "نام پروژه",
+ "projectNamePlaceholder": "my-app",
+ "create": "ساخت پروژه",
+ "creating": "در حال ساخت…",
+ "createdToast": "پروژه از {count} سرویس ساخته شد",
+ "migrate": "مهاجرت",
+ "migrateN": "مهاجرت {n} پروژه",
+ "migrating": "در حال مهاجرت…",
+ "addProject": "افزودن پروژه",
+ "removeProject": "حذف پروژه",
+ "targetLabel": "سرور مقصد",
+ "sameServer": "همان سرور (استفادهٔ مجدد از حجمها درجا)",
+ "crossServerBuiltInfo": "ایمیجهای ساختهشدهٔ محلی بهصورت داده به مقصد استریم میشوند (بدون رجیستری، بدون بازسازی). ایمیجهای بزرگ بیشتر طول میکشند — نوار پیشرفت انتقال را نشان میدهد.",
+ "killOriginals": "پس از تأیید استقرار جدید، کانتینرهای قدیمی را متوقف و حذف کن",
+ "transfer": {
+ "label": "انتقال",
+ "default": "مستقیم سرور به سرور (سریعترین)",
+ "auto": "خودکار",
+ "stream": "رله از طریق میزبان کنترل (پشتیبان)",
+ "direct": "مستقیم",
+ "rsync": "rsync"
+ },
+ "plan": {
+ "title": "طرح انتقال",
+ "total": "جمع",
+ "volume": "حجم",
+ "bind": "پوشه",
+ "image": "ایمیج",
+ "path": "مسیر",
+ "unknown": "اندازه نامعلوم",
+ "empty": "چیزی برای انتقال نیست.",
+ "compress": "فشردهسازی (rsync -z)",
+ "sslTitle": "گواهیهای SSL",
+ "sslReuse": "گواهی پیدا شد، دوباره استفاده میشود",
+ "sslIssue": "گواهی نیست — هنگام انتشار صادر میشود",
+ "pathsTitle": "مسیرهای اضافه",
+ "pathAdd": "افزودن",
+ "pathRemove": "حذف مسیر",
+ "pathSrcPlaceholder": "/source/path روی سرور قدیمی",
+ "pathDestPlaceholder": "/dest/path روی سرور جدید",
+ "conflictTitle": "مقصد از قبل داده دارد",
+ "conflictDesc": "حجم این سرویسها از قبل روی مقصد وجود دارد. برای هر کدام انتخاب کنید:",
+ "conflictOverride": "بازنویسی",
+ "conflictOverrideHint": "با منبع بازنویسی شود",
+ "conflictClone": "کلون",
+ "conflictCloneHint": "حجم جدید، قدیمی بماند",
+ "conflictKeep": "نگه داشتن",
+ "conflictKeepHint": "استفاده از دادهٔ موجود",
+ "measuring": "در حال اندازهگیری حجم انتقال…"
+ },
+ "steps": {
+ "select": "انتخاب",
+ "source": "منبع",
+ "domains": "پیکربندی",
+ "next": "بعدی",
+ "back": "بازگشت",
+ "sourceHint": "اختیاری است مخزنی را پیوند دهید و کانتینرها را به سرویسهای Compose آن نگاشت کنید — ایمیج در حال اجرا همچنان دوباره استفاده میشود.",
+ "linkRepo": "پیوند مخزن",
+ "linkRepoDesc": "مخزن را پیوند دهید تا Openship منبع هر سرویس را بداند (استقرار خودکار + بازسازی بعداً). این مهاجرت همچنان ایمیج در حال اجرا را دوباره استفاده میکند — الان بازسازی نمیشود.",
+ "repoOptional": "اختیاری",
+ "connectGithub": "اتصال GitHub",
+ "selectRepo": "یک مخزن انتخاب کنید",
+ "repoUrlPlaceholder": "یا github.com/owner/repo را بچسبانید",
+ "repoUrlInvalid": "مخزن GitHub را بهصورت owner/repo یا URL مربوط به github.com وارد کنید.",
+ "repoUrlAdd": "افزودن",
+ "branch": "شاخه",
+ "unlinkRepo": "حذف",
+ "mapTitle": "نگاشت کانتینرها به سرویسهای Compose",
+ "mapHint": "هر کانتینر کشفشده را به سرویسش در Compose مخزن اشاره دهید. مسیر ساخت سرویس منطبق منبع آن میشود.",
+ "mapToService": "انتخاب سرویس",
+ "notInRepo": "در مخزن نیست",
+ "composeServicesTitle": "سرویسهای مخزن",
+ "noComposeFound": "docker-compose در این مخزن پیدا نشد — مخزن در سطح پروژه پیوند میخورد.",
+ "mapNoRepo": "در گام انتخاب یک مخزن پیوند دهید تا کانتینرها به سرویسهای Compose آن نگاشت شوند (اختیاری).",
+ "mapSkipHint": "پیوند مخزن اختیاری است — برای ادامه رد کنید (ایمیج در حال اجرا همانطور دوباره استفاده میشود).",
+ "mapField": "سرویس مخزن",
+ "repoConnectHint": "در پنل به GitHub وصل شوید تا مخازن را بگردید — یا URL مخزن را بچسبانید.",
+ "repoPasteHint": "یا URL مخزن را بچسبانید.",
+ "repoOnSourceHint": "در گام بعدی (منبع) یک مخزن Git پیوند دهید — اختیاری.",
+ "parsingCompose": "در حال تجزیهٔ Compose…",
+ "envTitle": "متغیرهای محیط",
+ "publishDomains": "انتشار دامنهها",
+ "routeTitle": "مسیر",
+ "routeKeep": "نگه داشتن",
+ "routeFree": "زیردامنه",
+ "routeCustom": "سفارشی",
+ "routeNone": "هیچکدام",
+ "sslOn": "SSL",
+ "sslOff": "بدون SSL",
+ "internalOnly": "فقط داخلی — بهصورت عمومی در معرض نیست.",
+ "serviceNewBadge": "جدید",
+ "deployActionReuse": "استفاده مجدد از ایمیج",
+ "deployActionBuild": "ساخته میشود",
+ "deployActionPull": "کشیده میشود"
+ }
+ },
+ "run": {
+ "title": "در حال مهاجرت پشتهٔ شما",
+ "queued": "در صف…",
+ "adopting": "در حال ساخت پروژهٔ Openship…",
+ "moving_data": "در حال انتقال دادهٔ حجم به مقصد…",
+ "deploying": "در حال استقرار روی سرور مقصد…",
+ "verifying": "در حال تأیید استقرار جدید…",
+ "awaiting_cutover": "استقرار جدید زنده است — آن را بررسی کنید، سپس تمام کنید",
+ "cutover": "در حال حذف کانتینرهای اصلی…",
+ "succeeded": "مهاجرت کامل شد",
+ "allSucceeded": "{n} پروژه مهاجرت شد",
+ "queueHeader": "پروژه {index} از {total} · {name}",
+ "failed": "مهاجرت ناموفق بود",
+ "rolled_back": "برگشت داده شد — کانتینرهای اصلی دوباره راهاندازی شدند",
+ "openProject": "باز کردن پروژه",
+ "addDomains": "افزودن دامنهها",
+ "viewDeployLogs": "مشاهدهٔ گزارش استقرار",
+ "deployDetail": "گزارش استقرار",
+ "routeHint": "سرویسهای شما وارد شدهاند اما هنوز عمومی نیستند. در زبانهٔ دامنهها برای هر سرویس دامنه اضافه کنید — Openship بهصورت خودکار استقرار میکند تا لبه (OpenResty) را نصب کند و اگر پروکسی قدیمی هنوز ۸۰/۴۴۳ را نگه داشته باشد، پیشنهاد میدهد آن را تحویل بگیرد.",
+ "downtimeNote": "کانتینرهای اصلی هنگام انتقال متوقف میشوند؛ تا زنده شدن استقرار جدید انتظار قطعی کوتاه داشته باشید.",
+ "movingImage": "ایمیج",
+ "movingVolume": "حجم",
+ "partial": "جزئی — بعضی مسیرها منتقل نشدند"
+ },
+ "cutover": {
+ "title": "پایان مهاجرت",
+ "warning": "استقرار جدید روی سرور مقصد زنده است. میتوانید الان کانتینرهای اصلی را متوقف و حذف کنید، یا بهعنوان شبکهٔ ایمنی نگه دارید و بعداً حذف کنید — روی سرور مقصد متفاوت بهعنوان آمادهٔ زنده دوباره راهاندازی میشوند؛ روی همان سرور متوقف میمانند (پشتهٔ جدید حالا پورتهایشان را دارد).",
+ "stopRemove": "توقف و حذف اصلیها",
+ "keep": "نگه داشتن اصلیها",
+ "working": "در حال انجام…"
+ },
+ "discover": {
+ "buildBlocked": "از منبع ساخته شده — هنوز نمیتوان مهاجرت کرد (ابتدا یک ایمیج منتشر کنید)",
+ "proxyExcluded": "پروکسی معکوس ({ports}) — وارد نمیشود؛ لبهٔ Openship جایگزین آن میشود.",
+ "edgePortReserved": "پورتهای {ports} برای لبهٔ Openship رزرو شدهاند؛ منتشر نمیشوند.",
+ "nothing": "کانتینر Docker قابل پذیرشی روی این سرور پیدا نشد.",
+ "servicesTitle": "سرویسها",
+ "volumesTitle": "حجمها",
+ "networksTitle": "شبکهها",
+ "warningsTitle": "قبل از مهاجرت",
+ "composeProjects": "پروژههای Compose",
+ "composeGroup": "Compose",
+ "standaloneGroup": "کانتینرهای مستقل",
+ "standalone": "مستقل",
+ "selectAll": "انتخاب همه",
+ "selectedCount": "{n} از {total} انتخابشده",
+ "claimedIn": "در {project}",
+ "otherComposeHint": "بخشی از {group} — برای ورودش یک پروژهٔ جدا اضافه کنید",
+ "emptyProject": "هنوز سرویسی انتخاب نشده — از سمت چپ انتخاب کنید.",
+ "running": "در حال اجرا",
+ "stopped": "متوقف",
+ "image": "ایمیج",
+ "build": "ساخت",
+ "ports": "پورتها",
+ "env": "محیط",
+ "volumes": "حجمها",
+ "dependsOn": "وابسته به",
+ "inUseBy": "استفادهشده توسط",
+ "reused": "درجا دوباره استفاده میشود",
+ "volumeReuse": "تحویل گرفتن",
+ "volumeCopy": "کپی",
+ "volumeCopyHint": "تحویل گرفتن حجم موجود را درجا دوباره استفاده میکند. کپی آن را در یک حجم جدید Openship تکثیر میکند و اصلی را دستنخورده میگذارد (دیسک اضافه مصرف میکند).",
+ "bind": "مسیر میزبان",
+ "nVolumes": "{n} حجم",
+ "nEnv": "{n} متغیر محیط",
+ "envFromImage": "{n} متغیر از ایمیج میآید",
+ "envFromImageHint": "بهعنوان پیکربندی وارد نمیشود — ایمیج آنها را هنگام اجرا تأمین میکند. برای قفل کردن مقادیر قبل از بازسازی از منبع، آنها را وارد کنید.",
+ "envFromImageImport": "بههرحال وارد کن",
+ "alreadyManaged": "{count} کانتینر که از قبل توسط Openship مدیریت میشدند رد شدند."
+ },
+ "reimport": {
+ "title": "پروژههای Openship پیدا شد",
+ "intro": "این پروژههای Openship روی این سرور اجرا میشوند اما در این نمونه نیستند (پایگاهدادهٔ بازنشانیشده، یا سروری از Openship دیگر). برای بازیابی دوباره وارد کنید — شناسهٔ اصلی حفظ میشود تا کانتینرهای زنده دوباره متصل شوند.",
+ "services": "{n} سرویس",
+ "lastSeen": "آخرین استقرار {when}",
+ "fullRestore": "بازیابی کامل",
+ "bestEffort": "بازیابی تقریبی",
+ "action": "ورود دوباره",
+ "working": "در حال ورود دوباره…",
+ "reimported": "دوباره وارد شد",
+ "openProject": "باز کردن پروژه",
+ "alreadyManaged": "{n} کانتینر از قبل توسط این نمونه مدیریت میشوند.",
+ "finalizeNote": "ورود دوباره پروژه را بازیابی میکند و کانتینرهای در حال اجرا را زنده دوباره متصل میکند — نیازی به استقرار مجدد نیست. فقط برای تغییر ساخت دوباره استقرار کنید.",
+ "failed": "ورود دوباره ناموفق بود"
+ },
+ "scanFailed": "بازرسی Docker ناموفق بود",
+ "adoptFailed": "نتوانستیم پروژه را بسازیم",
+ "entry": {
+ "cardTitle": "سرویسهای موجود",
+ "cardDesc": "یک استقرار در حال اجرای Docker یا Compose را به Openship مهاجرت دهید.",
+ "action": "مهاجرت اپها",
+ "tab": "سرور موجود",
+ "comingSoon": "مهاجرت Docker بهزودی میآید"
+ },
+ "view": {
+ "tabView": "مشاهدهٔ سرویسها",
+ "tabMigrate": "مهاجرت",
+ "adoptedGroup": "مدیریتشده توسط {project}",
+ "openProject": "باز کردن پروژه",
+ "migrateToManage": "برای مدیریت مهاجرت کنید",
+ "back": "بازگشت به سرویسها"
+ },
+ "tab": {
+ "new": "مهاجرت جدید",
+ "empty": "هنوز مهاجرتی نیست",
+ "emptyDesc": "یک پشتهٔ در حال اجرای Docker یا Compose را از این سرور به Openship منتقل کنید، یا از سرور دیگری بیاورید.",
+ "close": "بستن",
+ "sessionLog": "گزارش نشست",
+ "noLogs": "هنوز خروجی گزارشی نیست.",
+ "detailTitle": "مهاجرت",
+ "inPlace": "درجا",
+ "crossServer": "بینسروری",
+ "servicesCount": "{n} سرویس",
+ "cancelRun": "لغو مهاجرت",
+ "confirmCancel": "این مهاجرت لغو شود؟ انتقال متوقف میشود و هر تغییری روی مقصد برگشت داده میشود.",
+ "inFlight": "در حال انجام",
+ "status": {
+ "queued": "در صف",
+ "adopting": "پذیرش",
+ "moving_data": "انتقال داده",
+ "deploying": "استقرار",
+ "verifying": "تأیید",
+ "awaiting_cutover": "در انتظار قطعووصل",
+ "cutover": "قطعووصل",
+ "succeeded": "موفق",
+ "failed": "ناموفق",
+ "rolled_back": "برگشتدادهشده",
+ "partial": "جزئی"
+ },
+ "delete": "حذف رکورد",
+ "confirmDelete": "این رکورد مهاجرت حذف شود؟ گزارش حذف میشود — پروژه و دادههایش دستنخورده میمانند.",
+ "back": "بازگشت به مهاجرتها",
+ "pendingTitle": "مسیرهایی که منتقل نشدند",
+ "pendingMissing": "روی منبع پیدا نشد",
+ "pendingError": "انتقال ناموفق بود",
+ "editPath": "ویرایش مسیر منبع",
+ "overridePlaceholder": "مسیر منبع جدید (خالی = تلاش دوباره همانطور)",
+ "skip": "رد کردن",
+ "undoSkip": "نگه داشتن",
+ "resume": "ازسرگیری مهاجرت",
+ "resuming": "در حال ازسرگیری…",
+ "editRetry": "ویرایش و تلاش دوباره",
+ "safetyTitle": "غیرمخرب و برگشتپذیر.",
+ "safetyBody": "داده کپی میشود، هرگز جابهجا نمیشود — منبع فقط کوتاه متوقف میشود و چیزی حذف نمیشود مگر اینکه صریحاً قطعووصل کنید.",
+ "cleanupTarget": "حذف دادهٔ کپیشده از مقصد",
+ "retryRun": "تلاش دوباره",
+ "changeTarget": "تغییر مقصد"
+ },
+ "plan": {
+ "missing": "پیدا نشد"
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/fa/misc.json b/apps/dashboard/src/i18n/locales/fa/misc.json
new file mode 100644
index 000000000..7bd3d096f
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/fa/misc.json
@@ -0,0 +1,370 @@
+{
+ "authorize": {
+ "invalidRequest": "درخواست نامعتبر",
+ "missingParams": "پارامترهای مجوز موجود نیست.",
+ "title": "مجوز دادن به {app}",
+ "wantsToConnect": "{app} میخواهد به حساب شما متصل شود.",
+ "wantsToConnectOnMachine": "{app} روی {machine} میخواهد به حساب شما متصل شود.",
+ "signedInAs": "واردشده بهعنوان",
+ "willAllow": "این اجازه میدهد:",
+ "permDeploy": "استقرار و مدیریت اپها",
+ "permAccess": "دسترسی به پروژهها و تنظیماتتان",
+ "authorize": "مجوز دادن",
+ "cancel": "لغو"
+ },
+ "mcpAuthorize": {
+ "title": "مجوز دادن به کلاینت MCP",
+ "subtitle": "یک کلاینت MCP میخواهد به حساب Openship شما متصل شود.",
+ "signedInAs": "واردشده بهعنوان",
+ "client": "کلاینت",
+ "organization": "سازمان",
+ "orgScopeNote": "کلاینت فقط میتواند داخل این سازمان عمل کند.",
+ "switchOrgError": "تغییر سازمان ممکن نشد. لطفاً دوباره تلاش کنید.",
+ "thisOrganization": "این سازمان",
+ "missingClientId": "client_id موجود نیست — این لینک مجوز نامعتبر است.",
+ "signInRequiredTitle": "برای ادامه وارد شوید",
+ "signInRequiredBody": "نتوانستیم نشست شما را در این صفحه تأیید کنیم، بنابراین به کلاینت مجوز داده نشد. دوباره وارد شوید تا درخواست ادامه یابد.",
+ "signInAction": "ورود",
+ "templatesHeading": "انتخاب کنید این کلاینت چه کارهایی بتواند بکند",
+ "templatesNote": "فقط دسترسیای را میتوانید بدهید که خودتان دارید.",
+ "recommendedBadge": "پیشنهادی",
+ "broadBadge": "گسترده",
+ "editedBadge": "ویرایششده",
+ "resetTemplate": "بازنشانی",
+ "tplAgentTitle": "عامل کدنویسی",
+ "tplAgentDesc": "پروژههای تازه میسازد و استقرار میدهد. چیزی که از قبل وجود دارد را نمیبیند.",
+ "tplExistingTitle": "پروژههای موجود",
+ "tplExistingDesc": "برای پروژههایی که انتخاب میکنید استقرار میدهد، راهاندازی دوباره میکند و لاگ میخواند. هیچ چیز دیگر.",
+ "tplReadOnlyTitle": "فقطخواندنی",
+ "tplReadOnlyDesc": "هر چیزی را که شما در {org} میتوانید بخوانید میخواند. هر نوشتنی مسدود است.",
+ "tplFullTitle": "دسترسی کامل به حساب",
+ "tplFullDesc": "در {org} بهجای شما عمل میکند — هر پروژه، سرور و ابزار. فقط با اعتماد کامل.",
+ "tplCustomTitle": "سفارشی",
+ "tplCustomDesc": "ترکیب خودتان از منابع.",
+ "editorHeading": "تنظیم دقیق دسترسی",
+ "editorNote": "تغییرات بلافاصله در خلاصه ظاهر میشوند.",
+ "capabilitiesHeading": "قابلیتها",
+ "createProjectsTitle": "ساخت پروژههای تازه",
+ "createProjectsDesc": "کلاینت میتواند پروژه بسازد و پروژههایی را که میسازد کاملاً مدیریت کند — از جمله حذف آنها. پروژههایی را که نساخته نمیبیند.",
+ "resourcesHeading": "منابع",
+ "advancedNote": "اینها هیچ ابزار MCP را باز نمیکنند، اما همان مجوز همچنان میتواند از طریق REST API به آنها برسد.",
+ "summaryHeading": "این کلاینت چه چیزی میگیرد",
+ "summaryEmptyTitle": "هنوز چیزی اعطا نشده",
+ "summaryEmptyBody": "منابع را از سمت مقابل انتخاب کنید، یا یک قالب از بالا برگزینید.",
+ "summaryUnscopedTitle": "هر چیزی که در {org} به آن دسترسی دارید",
+ "summaryUnscopedBody": "کلاینت با نقش خود شما عمل میکند. هرگز محدودتر از شما نیست.",
+ "summaryReadOnlyTitle": "فقطخواندنی در سراسر {org}",
+ "summaryReadOnlyBody": "کلاینت هر چیزی را که شما میتوانید بخوانید میخواند، و هیچ چیز نمینویسد.",
+ "capabilityRowTitle": "ساخت پروژههای تازه",
+ "capabilityRowDesc": "و پروژههایی را که میسازد کاملاً مدیریت کند",
+ "levelView": "مشاهده",
+ "levelManage": "استقرار و مدیریت",
+ "levelFull": "کنترل کامل",
+ "removeGrant": "حذف",
+ "wildcardTag": "همه",
+ "grantCountOne": "{count} منبع",
+ "grantCountOther": "{count} منبع",
+ "canHeading": "میتواند",
+ "cannotHeading": "نمیتواند",
+ "dgCreateProjects": "پروژههای تازه در {org} بسازد",
+ "dgManageOwnProjects": "پروژههایی را که میسازد کاملاً مدیریت کند، از جمله حذف آنها",
+ "dgOperatePickedProjects": "پروژههای فهرستشده در بالا را استقرار دهد، راهاندازی دوباره کند و تغییر دهد",
+ "dgViewPickedProjects": "پروژههای فهرستشده در بالا را بخواند — پیکربندی، لاگها و وضعیت",
+ "dgReadGithub": "مخزنهای GitHub شما را ببیند و نحوهٔ ساختشان را تشخیص دهد",
+ "dgWriteGithub": "مخزنهای GitHub شما را ببیند و تغییر دهد",
+ "dgServers": "از طریق API به سرورهای فهرستشده در بالا برسد",
+ "dgBackups": "به مقصدهای پشتیبان فهرستشده در بالا برسد",
+ "dgEverything": "هر کاری که شما در {org} میتوانید بکنید انجام دهد",
+ "dgReadEverything": "هر چیزی را که شما در {org} میتوانید بخوانید بخواند",
+ "dgNotExistingProjects": "هیچ پروژهای را که نساخته ببیند یا تغییر دهد",
+ "dgNotEnumerateProjects": "پروژههایتان را فهرست کند — فقط به موارد نامبرده در بالا میرسد",
+ "dgNotServers": "به سرورها، ایمیل یا تنظیمات سازمانتان دست بزند",
+ "dgNotDelete": "چیزی را حذف کند",
+ "dgNoWrites": "چیزی را استقرار دهد، تغییر دهد یا حذف کند",
+ "readOnly": "فقطخواندنی",
+ "readOnlyDesc": "هر نوشتنی را مسدود کن، صرفنظر از آنچه بالا فهرست شده.",
+ "readOnlySecretsNote": "فقطخواندنی همچنان اسراری را که کلاینت میتواند به آنها برسد افشا میکند، از جمله مقادیر متغیرهای محیطی و رشتههای اتصال.",
+ "readOnlyConflictCreate": "فقطخواندنی ساخت پروژه را مسدود میکند، بنابراین «ساخت پروژههای تازه» هیچ کاری نمیکند.",
+ "readOnlyConflictWritesOne": "فقطخواندنی نوشتن را مسدود میکند، بنابراین دسترسی نوشتن روی {count} منبع هیچ کاری نمیکند.",
+ "readOnlyConflictWritesOther": "فقطخواندنی نوشتن را مسدود میکند، بنابراین دسترسی نوشتن روی {count} منبع هیچ کاری نمیکند.",
+ "turnOffReadOnly": "خاموش کردن فقطخواندنی",
+ "blockedNoResources": "این اصلاً هیچ دسترسیای نمیدهد. حداقل یک منبع انتخاب کنید، یا یک قالب برگزینید.",
+ "blockedNoPermissions": "دسترسی همهٔ منابع پاک شده است. یک سطح بگذارید، یا ردیف را حذف کنید.",
+ "authorizeError": "تکمیل مجوز ممکن نشد. لطفاً دوباره تلاش کنید.",
+ "grantRejectedTitle": "حساب شما نمیتواند آن را اعطا کند",
+ "grantRejected": "{resource} حذف شد چون دسترسی خودتان آن را پوشش نمیدهد. خلاصه را بازبینی کنید و دوباره مجوز بدهید.",
+ "deny": "رد",
+ "authorize": "مجوز دادن",
+ "dgReadSource": "محتوای فایل را از مسیرهایی که محدوده کردید بخواند",
+ "dgNotReadSource": "محتوای هیچ فایلی از مخزنهایتان را بخواند",
+ "levelViewHint": "فقط خواندن: تنظیمات، لاگها و فراداده. بدون تغییر.",
+ "levelManageHint": "استقرار، راهاندازی دوباره و تغییر تنظیمات. نمیتواند حذف کند.",
+ "levelFullHint": "همهچیز، از جمله حذف خود منبع."
+ },
+ "acceptInvite": {
+ "invalidInvitation": "دعوتنامه نامعتبر",
+ "invitationStatus": "این دعوتنامه {status} است.",
+ "wrongAccount": "این دعوت برای {email} است. شما بهعنوان {currentEmail} وارد شدهاید.",
+ "loadFailed": "بارگذاری دعوتنامه ناموفق بود",
+ "acceptFailed": "پذیرش دعوتنامه ناموفق بود",
+ "theOrganization": "سازمان",
+ "invitedTitle": "دعوت شدهاید",
+ "needsLoginPre": "به ",
+ "needsLoginMid": " در Openship بپیوندید. با ",
+ "needsLoginPost": " وارد شوید یا حساب بسازید تا بپذیرید.",
+ "signIn": "ورود",
+ "createAccount": "ساخت حساب",
+ "joinTitle": "پیوستن به {org}",
+ "readyPre": "برای پیوستن به ",
+ "readyMid": " بهعنوان ",
+ "readyPost": " دعوت شدهاید.",
+ "decline": "رد کردن",
+ "accept": "پذیرش",
+ "joining": "در حال پیوستن به سازمان...",
+ "acceptedTitle": "عضو شدید",
+ "acceptedRedirect": "در حال انتقال به داشبورد...",
+ "errorTitle": "نمیتوان این دعوت را پذیرفت",
+ "backToDashboard": "بازگشت به داشبورد"
+ },
+ "cloudAuthorize": {
+ "missingRedirect": "پارامتر redirect موجود نیست.",
+ "missingState": "پارامتر state موجود نیست.",
+ "missingCodeChallenge": "پارامتر code_challenge موجود نیست.",
+ "invalidState": "پارامتر state نامعتبر است.",
+ "invalidCodeChallenge": "پارامتر code_challenge نامعتبر است.",
+ "invalidRedirectUrl": "آدرس ریدایرکت نامعتبر است.",
+ "redirectProtocol": "ریدایرکت باید از http یا https استفاده کند.",
+ "redirectHttps": "ریدایرکتهای غیرlocalhost باید از HTTPS استفاده کنند.",
+ "redirectUserinfo": "آدرس ریدایرکت نباید شامل userinfo باشد.",
+ "authorizeError": "مجوز دادن به این اتصال ممکن نشد.",
+ "invalidTitle": "درخواست مجوز نامعتبر",
+ "title": "مجوز دادن به دستگاه",
+ "instancePre": "یک نمونهٔ Openship در ",
+ "instancePost": " میخواهد به حساب Openship Cloud شما متصل شود.",
+ "signedInAs": "واردشده بهعنوان",
+ "authorizing": "در حال مجوز دادن…",
+ "authorize": "مجوز دادن",
+ "cancel": "لغو",
+ "deviceDoneTitle": "به Openship Cloud متصل شد",
+ "deviceDoneBody": "میتوانید این زبانه را ببندید و به ترمینال برگردید — راهاندازی بهصورت خودکار ادامه مییابد."
+ },
+ "cloudConnect": {
+ "noCode": "کد احراز هویت ارائه نشد. لطفاً دوباره تلاش کنید.",
+ "stateExpired": "وضعیت اتصال منقضی شد. لطفاً دوباره تلاش کنید.",
+ "verifyError": "تأیید با Openship Cloud ممکن نشد.",
+ "finalizingTitle": "در حال نهایی کردن اتصال…",
+ "finalizingSubtitle": "یک لحظه.",
+ "successTitle": "به Openship Cloud متصل شد",
+ "successDescription": "نمونهٔ شما اکنون پیوند شده است. میتوانید این پنجره را ببندید.",
+ "failedTitle": "اتصال ناموفق بود"
+ },
+ "iconPicker": {
+ "chooseIcon": "انتخاب آیکون",
+ "searchPlaceholder": "جستجوی آیکونها...",
+ "count": "{offset} از {total}",
+ "startSearching": "شروع جستجو",
+ "startSearchingHint": "یک کلیدواژه وارد کنید تا آیکون پیدا شود",
+ "searching": "در حال جستجوی آیکونها...",
+ "noIconsFound": "آیکونی پیدا نشد",
+ "noIconsHint": "عبارت جستجوی دیگری امتحان کنید",
+ "loadingMore": "در حال بارگذاری بیشتر...",
+ "noMoreResults": "نتیجهٔ دیگری نیست",
+ "cancel": "لغو",
+ "selectIcon": "گزینش آیکون",
+ "chooseAnIcon": "یک آیکون انتخاب کنید"
+ },
+ "restoreWizard": {
+ "startFailed": "شروع بازیابی ناموفق بود",
+ "typeToConfirm": "برای تأیید، نام سرویس را دقیقاً تایپ کنید",
+ "applyFailed": "اعمال بازیابی ناموفق بود",
+ "title": "بازیابی پشتیبان",
+ "serviceLabel": "سرویس: {name}",
+ "serviceRestore": "بازیابی سرویس",
+ "stepReview": "بازبینی",
+ "stepPrepare": "آمادهسازی",
+ "stepConfirm": "تأیید",
+ "stepApply": "اعمال",
+ "stepDone": "انجام شد",
+ "reviewPre": "در آستانهٔ بازیابی از پشتیبانِ گرفتهشده در ",
+ "reviewPost": " هستید.",
+ "sizeArtifacts": "حجم: {size} · آرتیفکتها: {count}",
+ "overwriteWarning": "این کار دادهٔ فعلی را بازنویسی میکند.",
+ "overwriteHint": "گام بعدی (آمادهسازی) بررسی میکند که پشتیبان قابل دانلود باشد. تا وقتی در گام اعمال تأیید نکنید هیچ کار مخربی رخ نمیدهد.",
+ "protectLabel": "محافظت از آخرین پشتیبان در برابر حذف دورهای",
+ "protectHint": "آخرین پشتیبان موفق را از حذف خودکار حفظ میکند تا اگر مشکلی پیش آمد بتوانید به «درست قبل از این بازیابی» برگردید.",
+ "cancel": "لغو",
+ "starting": "در حال شروع…",
+ "continuePrepare": "ادامه → آمادهسازی",
+ "verifying": "در حال تأیید پشتیبان…",
+ "verifyingHint": "وجود و یکپارچگی آرتیفکت در مقصد بررسی میشود. سرویس همچنان عادی اجرا میشود.",
+ "cancelRestore": "لغو بازیابی",
+ "verified": "پشتیبان تأیید شد، آمادهٔ اعمال.",
+ "preparedInfo": "{size} آماده شد. سرویس هنگام بازیابی برای مدت کوتاهی متوقف میشود.",
+ "confirmPre": "دادههای فعلی والیوم ",
+ "confirmStrong": "برای همیشه جایگزین میشود",
+ "confirmMid": ". برای تأیید ",
+ "confirmPost": " را تایپ کنید.",
+ "applying": "در حال اعمال…",
+ "applyRestore": "اعمال بازیابی",
+ "restoringData": "در حال بازیابی داده…",
+ "restoringHint": "سرویس متوقف میشود → جابهجایی داده → راهاندازی دوباره. این پنجره را نبندید.",
+ "cancelFailed": "لغو بازیابی ناموفق بود",
+ "cancelPending": "درخواست لغو ثبت شد. بازیابی در نزدیکترین نقطهٔ امن بعدی متوقف میشود — این پنجره نتیجه را نشان میدهد.",
+ "destructiveNotYet": "هنوز چیزی نوشته نشده، بنابراین لغو کردن الان امن است.",
+ "destructiveNow": "داده در حال نوشته شدن است. لغو کردن الان دادهٔ ناقص بهجا میگذارد.",
+ "abortRestore": "قطع بازیابی",
+ "abortTitle": "این بازیابی قطع شود؟",
+ "abortBodyClean": "بازیابی هنوز چیزی ننوشته است. لغو کردن الان دادههایتان را دقیقاً همانطور که هست باقی میگذارد؛ اگر سرویس را متوقف کرده باشیم دوباره راهاندازی میشود.",
+ "abortBodyDestructive": "بازیابی از قبل شروع به نوشتن کرده است. قطع کردن الان مقصد را با دادهٔ ناقص رها میکند و سرویس عمداً متوقف میماند — پیش از راهاندازی سرویس، این بازیابی (یا یک بازیابی تازهتر) را تا پایان دوباره اجرا کنید.",
+ "abortKeepGoing": "ادامهٔ بازیابی",
+ "abortConfirm": "بله، قطع کن",
+ "partialDataNotice": "مقصد بازیابی دادهٔ ناقص دارد و سرویس عمداً متوقف مانده است. پیش از راهاندازی سرویس، این بازیابی (یا یک بازیابی تازهتر) را تا پایان دوباره اجرا کنید.",
+ "restoreComplete": "بازیابی کامل شد.",
+ "restoreStatus": "بازیابی {status}",
+ "failed": "ناموفق",
+ "close": "بستن"
+ },
+ "terminalTabs": {
+ "shell": "شل {n}",
+ "close": "بستن {label}",
+ "serverLimit": "سقف سرور: {max} شل همزمان.",
+ "newShell": "شل تازه",
+ "noTerminals": "ترمینال بازی نیست.",
+ "expand": "گسترش",
+ "collapse": "جمع کردن"
+ },
+ "buildPage": {
+ "thisRepo": "این مخزن",
+ "notFoundTitle": "استقرار پیدا نشد",
+ "notFoundDescription": "این استقرار وجود ندارد یا به آن دسترسی ندارید. ممکن است حذف شده باشد یا لینک نادرست باشد.",
+ "viewDeployments": "مشاهدهٔ استقرارها",
+ "goHome": "رفتن به خانه"
+ },
+ "backups": {
+ "usedBy": "استفادهشده توسط",
+ "usedByDesc": "پروژهها و سرویسهایی که به این مقصد پشتیبان میگیرند",
+ "noUsageTitle": "هنوز چیزی اینجا پشتیبان نمیشود",
+ "noUsageDesc": "وقتی پروژه یا سرویسی به این مقصد پشتیبان بگیرد، اینجا ظاهر میشود.",
+ "scheduleManual": "دستی",
+ "projectDefault": "پیشفرض پروژه",
+ "mailServer": "سرور ایمیل",
+ "openInProject": "باز کردن در پروژه",
+ "notFound": "مقصد پیدا نشد",
+ "summaryTitle": "فضای ذخیرهسازی",
+ "summaryStored": "کل ذخیرهشده",
+ "summaryBackups": "پشتیبانها",
+ "summaryDestinations": "مقصدها",
+ "statsStored": "ذخیرهشده",
+ "statsBackups": "پشتیبان",
+ "statsNoRuns": "هنوز پشتیبان گرفته نشده",
+ "statsLast": "آخرین",
+ "title": "پشتیبانها",
+ "subtitle": "مقصدهای ذخیرهسازی خارجی برای پشتیبان پروژه و سرویس",
+ "addDestination": "افزودن مقصد",
+ "kindS3": "سازگار با S3",
+ "kindSftp": "SFTP",
+ "kindServer": "سرور موجود",
+ "kindLocal": "دیسک محلی",
+ "kindHttp": "آپلود HTTP",
+ "verifiedSuccess": "«{name}» با موفقیت تأیید شد.",
+ "verificationFailedMsg": "تأیید ناموفق بود.",
+ "verificationFailedTitle": "تأیید ناموفق بود",
+ "setDefaultSuccess": "«{name}» اکنون مقصد پیشفرض است.",
+ "setDefaultFailed": "تنظیم پیشفرض ممکن نشد",
+ "deletedSuccess": "«{name}» حذف شد.",
+ "deleteFailed": "حذف ناموفق بود",
+ "updated": "مقصد بهروز شد.",
+ "created": "مقصد ساخته شد.",
+ "verifyConnection": "تأیید اتصال",
+ "verifyingBadge": "در حال تأیید…",
+ "verifiedBadge": "تأییدشده",
+ "failedBadge": "ناموفق",
+ "notVerifiedBadge": "تأییدنشده",
+ "defaultBadge": "پیشفرض",
+ "defaultTitle": "مقصد پیشفرض",
+ "lastVerified": "آخرین تأیید {date}",
+ "editAction": "ویرایش",
+ "setDefaultAction": "تنظیم بهعنوان پیشفرض",
+ "deleteAction": "حذف",
+ "emptyTitle": "هنوز مقصد پشتیبان اضافه نشده",
+ "emptyDescription": "یک باکت سازگار با S3، یک میزبان SFTP، یک سرور موجود Openship، یا یک دیسک محلی وصل کنید — سپس سرویسها میتوانند روی آن پشتیبان بگیرند.",
+ "addFirst": "اولین مقصد خود را اضافه کنید",
+ "supportedTitle": "مقصدهای پشتیبانیشده",
+ "cardS3Sub": "AWS · R2 · B2 · MinIO",
+ "cardSftpSub": "هر میزبان SSH",
+ "cardServerSub": "استفادهٔ دوباره از اعتبار SSH",
+ "cardLocalSub": "فقط میزبانی شخصی",
+ "deleteTitle": "حذف مقصد",
+ "deletePre": "حذف ",
+ "deletePost": "؟ این کار برگشتناپذیر است. مقصدی که هنوز توسط یک سیاست پشتیبان استفاده میشود قابل حذف نیست.",
+ "cancel": "لغو",
+ "credAccessKeyStored": "کلید دسترسی ذخیره شده",
+ "credPrivateKeyStored": "کلید خصوصی ذخیره شده",
+ "credPasswordStored": "رمز عبور ذخیره شده",
+ "credNone": "بدون اعتبارنامه",
+ "credReusesServer": "از اعتبارنامهٔ سرور دوباره استفاده میکند",
+ "credNoneNeeded": "اعتبارنامهای لازم نیست",
+ "serverPrefix": "سرور ",
+ "s3Desc": "هر فضای ذخیرهسازی با API از نوع S3 — AWS S3، Cloudflare R2، Backblaze B2، Wasabi، MinIO.",
+ "s3Examples": "محبوبترین · همهجا کار میکند",
+ "sftpDesc": "پشتیبانها را از طریق SSH با اعتبارنامهٔ تازه به یک میزبان دلخواه بفرستید.",
+ "sftpExamples": "باکس پشتیبان اختصاصی · NAS",
+ "serverDesc": "اعتبارنامهٔ SSH سروری را که از قبل به Openship اضافه کردهاید دوباره استفاده کنید.",
+ "serverExamples": "اعتبارنامههای /servers را دوباره استفاده میکند",
+ "localDesc": "در یک پوشه روی میزبان API بنویسید. در حالت کلاد غیرفعال است.",
+ "localExamples": "فقط میزبانی شخصی",
+ "modalEditTitle": "ویرایش {name}",
+ "modalAddTitle": "افزودن مقصد پشتیبان",
+ "modalNewTitle": "{name} تازه",
+ "destinationFallback": "مقصد",
+ "modalPickSubtitle": "محل ذخیرهٔ پشتیبانها را انتخاب کنید.",
+ "modalConfigureSubtitle": "اعتبارنامهها در حالت سکون رمزنگاری میشوند و هرگز دوباره نمایش داده نمیشوند.",
+ "encryptedAtRest": "رمزنگاریشده در حالت سکون",
+ "backToPicker": "بازگشت به انتخاب نوع",
+ "secretStoredPlaceholder": "•••••• ذخیرهشده — برای نگه داشتن خالی بگذارید",
+ "updateFailed": "بهروزرسانی مقصد ناموفق بود",
+ "createFailed": "ساخت مقصد ناموفق بود",
+ "fieldName": "نام",
+ "providerLabel": "ارائهدهنده",
+ "providerAws": "AWS S3",
+ "providerR2": "Cloudflare R2",
+ "providerB2": "Backblaze B2",
+ "providerWasabi": "Wasabi",
+ "providerDo": "DigitalOcean Spaces",
+ "providerMinio": "MinIO / سفارشی",
+ "fieldAccountId": "شناسهٔ حساب",
+ "hintAccountId": "شناسهٔ حساب Cloudflare شما — برای ساخت نقطهٔ پایانی R2 استفاده میشود.",
+ "hintEndpointMinio": "آدرس کامل نقطهٔ پایانی S3، مثلاً https://minio.example.com:9000",
+ "testConnection": "آزمایش اتصال",
+ "testing": "در حال آزمایش اتصال…",
+ "testOk": "متصل شد — باکت در دسترس و قابل نوشتن است.",
+ "fieldEndpoint": "نقطهٔ پایانی",
+ "hintEndpoint": "خالی = AWS S3. R2: https://.r2.cloudflarestorage.com",
+ "fieldRegion": "منطقه",
+ "fieldBucket": "باکت",
+ "fieldPathPrefix": "پیشوند مسیر",
+ "hintPathPrefix": "زیرمسیر اختیاری داخل باکت",
+ "fieldAccessKeyId": "شناسهٔ کلید دسترسی",
+ "fieldSecretAccessKey": "کلید دسترسی محرمانه",
+ "fieldHost": "میزبان",
+ "fieldPort": "پورت",
+ "fieldUser": "کاربر",
+ "fieldPassword": "رمز عبور",
+ "hintPassword": "اگر از کلید خصوصی استفاده میکنید خالی بگذارید",
+ "fieldPrivateKey": "کلید خصوصی (PEM)",
+ "hintPrivateKey": "وقتی رمز عبور خالی است استفاده میشود",
+ "fieldServer": "سرور",
+ "hintServer": "اعتبارنامهٔ SSH ذخیرهشده در /servers را دوباره استفاده میکند",
+ "selectServer": "— یک سرور انتخاب کنید —",
+ "noServersPre": "هنوز سروری پیکربندی نشده. ابتدا یکی را در ",
+ "noServersLink": "سرورها",
+ "noServersPost": " اضافه کنید.",
+ "fieldRemotePath": "مسیر راه دور",
+ "hintRemotePath": "پوشهٔ مطلق روی سرور (مثلاً /backups/openship)",
+ "fieldAbsolutePath": "مسیر مطلق",
+ "hintAbsolutePath": "مسیر فایلسیستم روی میزبان API. در حالت کلاد غیرفعال است.",
+ "saving": "در حال ذخیره…",
+ "saveChanges": "ذخیرهٔ تغییرات",
+ "saveDestination": "ذخیرهٔ مقصد"
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/fa/onboarding.json b/apps/dashboard/src/i18n/locales/fa/onboarding.json
new file mode 100644
index 000000000..54537548a
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/fa/onboarding.json
@@ -0,0 +1,149 @@
+{
+ "common": {
+ "goBack": "بازگشت",
+ "continue": "ادامه",
+ "back": "بازگشت",
+ "optional": "(اختیاری)"
+ },
+ "topBar": {
+ "website": "وبسایت",
+ "github": "GitHub",
+ "theme": "تغییر پوسته",
+ "language": "زبان"
+ },
+ "choose": {
+ "title": "به Openship خوش آمدید",
+ "subtitle": "نحوه اجرای Openship را انتخاب کنید. هر دو گزینه همه قابلیتها را میدهند — و هر زمان میتوانید جابهجا شوید.",
+ "cloud": {
+ "name": "Openship Cloud",
+ "desc": "زیرساخت، بهروزرسانیها و پشتیبانگیری با ماست. فقط وارد شوید و استقرار را شروع کنید.",
+ "perk1": "بدون نیاز به سرور",
+ "perk2": "سطح رایگان همراه است",
+ "perk3": "مدیریتشده و همیشه بهروز",
+ "cta": "ادامه با Cloud"
+ },
+ "or": "یا",
+ "selfhost": {
+ "name": "میزبانی شخصی",
+ "desc": "Openship را روی سختافزار خود اجرا کنید. یک سرور راه دور متصل کنید یا از همین ماشین استفاده کنید.",
+ "perk1": "مالکیت کامل داده",
+ "perk2": "سرور راه دور یا همین Mac",
+ "perk3": "بقیه را ما انجام میدهیم",
+ "cta": "راهاندازی سرور من"
+ },
+ "migrateNote": "هر زمان با یک کلیک بین Cloud و میزبانی شخصی جابهجا شوید — بدون قفل شدن، هیچوقت.",
+ "skip": "فعلاً رد شو"
+ },
+ "selfhost": {
+ "title": "Openship کجا اجرا شود؟",
+ "subtitleLine1": "محل نصب پلتفرم Openship را انتخاب کنید.",
+ "subtitleLine2": "هر دو گزینه کنترل کامل و مالکیت داده را میدهند.",
+ "local": {
+ "title": "همین ماشین",
+ "badge": "بتا",
+ "desc": "Openship را همینجا نصب کنید — بدون SSH، بدون راهاندازی راه دور",
+ "comingSoon": "بهزودی",
+ "comingSoonDesc": "اجرای مستقیم Openship روی این ماشین هنوز آماده نیست — فعلاً یک سرور متصل کنید."
+ },
+ "remote": {
+ "title": "سروری دیگر",
+ "desc": "از طریق SSH به سرور راه دور، VPS یا هر ماشین در شبکهتان متصل شوید"
+ }
+ },
+ "ssh": {
+ "title": "به سرور خود متصل شوید",
+ "subtitleLine1": "جزئیات سرور را وارد کنید و روش احراز هویت را انتخاب کنید.",
+ "subtitleLine2": "نصب و پیکربندی همه چیز را بهصورت خودکار انجام میدهیم.",
+ "serverNameLabel": "نام سرور",
+ "serverNamePlaceholder": "Production، Staging، Dev...",
+ "serverIpLabel": "آدرس IP سرور",
+ "usernameLabel": "نام کاربری",
+ "methodPassword": "رمز عبور",
+ "methodKey": "کلید SSH",
+ "methodAgent": "Agent",
+ "agentHint": "با SSH agent همین ماشین متصل میشود — مثل VSCode، بدون نیاز به رمز یا کلید. میزبانی که Openship را اجرا میکند باید از قبل به این سرور دسترسی داشته باشد (کلیدی که در ssh-agent آن بارگذاری شده).",
+ "passwordLabel": "رمز عبور",
+ "passwordPlaceholder": "رمز عبور سرور",
+ "keyPathLabel": "مسیر کلید",
+ "browse": "مرور",
+ "passphraseLabel": "عبارت عبور",
+ "passphrasePlaceholder": "اگر ندارد خالی بگذارید",
+ "advanced": "پیشرفته",
+ "sshPortLabel": "پورت SSH",
+ "jumpHostLabel": "میزبان واسط",
+ "extraArgsLabel": "آرگومانهای اضافه SSH",
+ "submit": "ذخیره و ادامه",
+ "tutorial": "آموزش گامبهگام راهاندازی",
+ "testConnection": "آزمایش اتصال",
+ "testing": "در حال آزمایش…",
+ "connected": "متصل شد",
+ "testFailed": "اتصال ناموفق بود",
+ "testHint": "قبل از ادامه، در دسترس بودن سرور را تأیید کنید."
+ },
+ "tunnel": {
+ "title": "دسترسی اینترنت",
+ "subtitle": "سرور شما برای وبهوکها، SSL و دسترسی عمومی باید از اینترنت قابلدسترسی باشد.",
+ "providers": {
+ "edge": {
+ "title": "Openship Edge",
+ "desc": "بدون پیکربندی. SSL خودکار، دسترسپذیری بالا و محافظت DDoS — همه داخلی.",
+ "badge": "پیشنهادی"
+ },
+ "cloudflare": {
+ "title": "Cloudflare Tunnel",
+ "desc": "از حساب Cloudflare خودتان استفاده کنید. سطح رایگان موجود است."
+ },
+ "ngrok": {
+ "title": "ngrok",
+ "desc": "سرویسها را با توکن احراز هویت ngrok در دسترس بگذارید."
+ }
+ },
+ "cloudflareTokenLabel": "توکن Cloudflare Tunnel",
+ "ngrokTokenLabel": "توکن احراز هویت ngrok",
+ "cloudflarePlaceholder": "توکن تونل را جایگذاری کنید",
+ "ngrokPlaceholder": "توکن احراز هویت ngrok را جایگذاری کنید",
+ "edgeHint": "Openship Edge به حساب Openship نیاز دارد. برای ادامه وارد میشوید.",
+ "signInContinue": "ورود و ادامه"
+ },
+ "preferences": {
+ "title": "ترجیحات ساخت",
+ "subtitleLine1": "نحوه ساخته شدن پروژهها هنگام استقرار را انتخاب کنید.",
+ "subtitleLine2": "بعداً میتوانید این را در تنظیمات تغییر دهید.",
+ "options": {
+ "auto": {
+ "title": "خودکار",
+ "desc": "بهترین گزینه را برای هر فریمورک انتخاب میکنیم"
+ },
+ "server": {
+ "title": "راه دور",
+ "desc": "مستقیماً روی سرور استقرار بسازید"
+ },
+ "local": {
+ "title": "محلی",
+ "desc": "روی این Mac بسازید، سپس به سرور بفرستید"
+ }
+ },
+ "hint": "استقرارهای Git push همیشه روی سرور ساخته میشوند — این فقط برای استقرارهای دستی است."
+ },
+ "loading": {
+ "connecting": "در حال اتصال…",
+ "verifying": "در حال تأیید سرور شما",
+ "retry": "تلاش دوباره",
+ "openingCloud": "در حال باز کردن Openship Cloud…",
+ "waitingAuth": "در انتظار تکمیل احراز هویت",
+ "couldNotStartAuthTitle": "شروع احراز هویت ابری ممکن نشد",
+ "authFailedTitle": "احراز هویت ابری ناموفق بود",
+ "couldNotStartAuthMsg": "جریان احراز هویت دسکتاپ شروع نشد.",
+ "authFailedMsg": "دوباره تلاش کنید و ورود را در مرورگر تمام کنید.",
+ "completingSignIn": "در حال تکمیل ورود…",
+ "returningToOpenship": "بازگشت به Openship",
+ "redirectingCloud": "در حال هدایت به Openship Cloud…",
+ "newTabSignIn": "ورود را در زبانه جدید تمام میکنید",
+ "couldNotSaveTitle": "ذخیره پیکربندی ممکن نشد",
+ "couldNotSaveMsg": "API پاسخ نداد. مطمئن شوید سرویسها در حال اجرا هستند.",
+ "savingConfig": "در حال ذخیره پیکربندی…",
+ "almostThere": "تقریباً تمام است",
+ "settingUpAccount": "در حال راهاندازی حساب شما…",
+ "creatingSession": "در حال ایجاد نشست شما"
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/fa/overview.json b/apps/dashboard/src/i18n/locales/fa/overview.json
new file mode 100644
index 000000000..5208b660b
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/fa/overview.json
@@ -0,0 +1,144 @@
+{
+ "activity": {
+ "title": "فعالیت",
+ "thisWeek": "این هفته",
+ "deploymentSingular": "{count} استقرار",
+ "deploymentPlural": "{count} استقرار"
+ },
+ "apiRequests": {
+ "title": "درخواستهای API",
+ "last7Days": "۷ روز گذشته",
+ "totalRequests": "کل درخواستها",
+ "requestsTooltip": "{count} درخواست"
+ },
+ "credits": {
+ "title": "مانده اعتبار",
+ "availableToUse": "آماده استفاده",
+ "spentThisMonth": "مصرفشده این ماه",
+ "add": "افزودن"
+ },
+ "deployments": {
+ "title": "استقرارها",
+ "lastNDays": "{period} روز گذشته",
+ "deploymentSingular": "{count} استقرار",
+ "deploymentPlural": "{count} استقرار"
+ },
+ "homeTip": {
+ "quickTip": "نکته سریع",
+ "connectText": "مخزن GitHub را برای استقرار خودکار روی هر push متصل کنید.",
+ "connectLabel": "اتصال GitHub",
+ "createText": "اولین پروژه را بسازید تا استقرار را شروع کنید.",
+ "createLabel": "پروژه جدید",
+ "settingsText": "متغیرهای محیط و دامنههای سفارشی را در تنظیمات پروژه تنظیم کنید.",
+ "settingsLabel": "تنظیمات پروژه",
+ "connected": "متصل",
+ "tips": {
+ "envVars": {
+ "text": "متغیرهای محیط رمزگذاریشده و رازها را به هر پروژه اضافه کنید.",
+ "label": "باز کردن پروژهها"
+ },
+ "customDomain": {
+ "text": "یک دامنه سفارشی را به پروژه اشاره دهید — HTTPS بهصورت خودکار صادر میشود.",
+ "label": "باز کردن پروژهها"
+ },
+ "autoDeploy": {
+ "text": "GitHub را متصل کنید تا هر push خودکار مستقر شود، بدون راهاندازی CI.",
+ "label": "باز کردن تنظیمات Git"
+ },
+ "rollback": {
+ "text": "با یک کلیک به هر استقرار قبلی بازگردید.",
+ "label": "مشاهده استقرارها"
+ },
+ "apps": {
+ "text": "اپهای میزبانی شخصی مثل Gitea، n8n و Metabase را با یک کلیک نصب کنید.",
+ "label": "مرور اپها"
+ },
+ "servers": {
+ "text": "سرورهای خود را از طریق SSH متصل کنید و از اینجا روی آنها مستقر شوید.",
+ "label": "مدیریت سرورها"
+ },
+ "jobs": {
+ "text": "فرمانها را با cron زمانبندی کنید — تلاش دوباره، لاگ زنده و تاریخچه همراه است.",
+ "label": "باز کردن کارها"
+ },
+ "backups": {
+ "text": "پشتیبانگیری خودکار برای پایگاهداده و volumeها را تنظیم کنید.",
+ "label": "پیکربندی پشتیبانها"
+ },
+ "mail": {
+ "text": "سرور ایمیل و وبمیل خودتان را کاملاً روی زیرساخت خودتان اجرا کنید.",
+ "label": "راهاندازی ایمیل"
+ },
+ "team": {
+ "text": "همتیمیها را دعوت کنید و دسترسیشان را بهازای هر پروژه محدود کنید.",
+ "label": "مدیریت تیم"
+ }
+ }
+ },
+ "welcome": {
+ "title": "اولین پروژه را راهاندازی کنید",
+ "subtitle": "یک مخزن متصل کنید یا از قالب شروع کنید — Openship آن را میسازد، مستقر میکند و ظرف چند دقیقه یک URL زنده به شما میدهد.",
+ "createProject": "ایجاد پروژه",
+ "importGithub": "وارد کردن از GitHub",
+ "tipPrefix": "نکته: بزنید",
+ "tipSuffix": "تا به هر جا بروید"
+ },
+ "header": {
+ "goodMorning": "صبح بخیر",
+ "goodAfternoon": "ظهر بخیر",
+ "goodEvening": "عصر بخیر",
+ "greetingWithName": "{greeting}، {name}",
+ "overview": "نمای کلی",
+ "subtitle": "مصرف پلتفرم را پایش کنید و منابع را مدیریت کنید",
+ "creditsBalance": "مانده اعتبار",
+ "topUp": "شارژ"
+ },
+ "hero": {
+ "title": "فعالیت پلتفرم",
+ "subtitle": "مصرف توکن و درخواستهای API",
+ "tokens": "توکنها",
+ "requests": "درخواستها",
+ "lastNDays": "{period} روز گذشته",
+ "agents": "عاملها",
+ "environments": "محیطها",
+ "total": "کل",
+ "production": "تولید",
+ "testing": "آزمایشی",
+ "prodCount": "{count} تولید",
+ "testCount": "{count} آزمایش"
+ },
+ "projects": {
+ "title": "پروژهها",
+ "activeDeployments": "استقرارهای فعال",
+ "total": "کل",
+ "live": "زنده",
+ "building": "در حال ساخت",
+ "needAttentionSingular": "{count} پروژه نیاز به توجه دارد",
+ "needAttentionPlural": "{count} پروژه نیاز به توجه دارند"
+ },
+ "sandboxes": {
+ "title": "Sandboxها",
+ "developmentEnvironments": "محیطهای توسعه",
+ "totalSandboxes": "کل sandboxها",
+ "active": "فعال",
+ "runningCount": "{count} در حال اجرا",
+ "stoppedCount": "{count} متوقف"
+ },
+ "stats": {
+ "creditsBalance": "مانده اعتبار",
+ "tokenUsage": "مصرف توکن",
+ "thisMonth": "این ماه",
+ "apiRequests": "درخواستهای API",
+ "last7Days": "۷ روز گذشته",
+ "deployments": "استقرارها",
+ "successfulCount": "{count} موفق",
+ "activeProjects": "پروژههای فعال",
+ "sandboxes": "Sandboxها",
+ "activeCount": "{count} فعال"
+ },
+ "tokenUsage": {
+ "title": "مصرف توکن",
+ "lastNDays": "{period} روز گذشته",
+ "tokensTooltip": "{count} توکن"
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/fa/projectDetail.json b/apps/dashboard/src/i18n/locales/fa/projectDetail.json
new file mode 100644
index 000000000..fec3bbaac
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/fa/projectDetail.json
@@ -0,0 +1,276 @@
+{
+ "general": {
+ "productionUrl": {
+ "title": "URL تولید",
+ "notConfigured": "پیکربندی نشده"
+ },
+ "identity": {
+ "title": "هویت پروژه",
+ "nameLabel": "نام پروژه",
+ "namePlaceholder": "my-awesome-project",
+ "unnamed": "پروژهٔ بدون نام",
+ "descriptionLabel": "توضیحات",
+ "descriptionPlaceholder": "یک برنامهٔ وب مدرن ساختهشده با...",
+ "noDescription": "توضیحی داده نشده",
+ "save": "ذخیره",
+ "cancel": "لغو",
+ "updateNameFailed": "بهروزرسانی نام پروژه ناموفق بود",
+ "updateDescriptionFailed": "بهروزرسانی توضیحات پروژه ناموفق بود"
+ },
+ "info": {
+ "title": "اطلاعات پروژه",
+ "projectId": "شناسهٔ پروژه",
+ "branch": "شاخه",
+ "lastDeploy": "آخرین استقرار",
+ "framework": "فریمورک",
+ "unknown": "نامشخص",
+ "aiTitle": "دستیار هوش مصنوعی",
+ "aiPoweredBy": "قدرتگرفته از Blurs",
+ "aiEdit": "ویرایش با Blurs",
+ "aiComingSoonTitle": "بهزودی",
+ "aiComingSoonBody": "ویرایش با Blurs هنوز در دسترس نیست"
+ },
+ "topPaths": {
+ "title": "مسیرهای برتر",
+ "showLess": "نمایش کمتر",
+ "requestsShort": "درخواست"
+ },
+ "traffic": {
+ "title": "نمای کلی ترافیک",
+ "last24Hours": "۲۴ ساعت گذشته",
+ "bar": "میلهای",
+ "area": "ناحیهای",
+ "loading": "در حال بارگذاری تحلیلها...",
+ "noDataTitle": "هنوز دادهٔ ترافیک نیست",
+ "noDataBody": "این نمودار پس از دریافت درخواست توسط اپ پر میشود."
+ }
+ },
+ "logs": {
+ "actions": {
+ "copied": "کپی شد",
+ "copy": "کپی",
+ "download": "دانلود",
+ "clear": "پاک کردن"
+ },
+ "server": {
+ "title": "لاگ درخواستهای HTTP",
+ "live": "زنده",
+ "errorHint": "مطمئن شوید اسکریپتهای تحلیل روی سرور مستقر شدهاند",
+ "waiting": "در انتظار درخواستهای ورودی…",
+ "streamError": "خطای استریم",
+ "connectionLost": "اتصال به استریم لاگ قطع شد",
+ "connectFailed": "اتصال به استریم لاگ ناموفق بود",
+ "noDomain": "برای دیدن لاگ درخواست یک دامنه اضافه کنید",
+ "noDomainHint": "لاگ درخواستهای HTTP بهازای هر دامنه جمع میشود — اول یک دامنه به این پروژه اضافه کنید."
+ },
+ "terminal": {
+ "fallbackName": "ترمینال",
+ "searchPlaceholder": "جستجو در لاگها...",
+ "previousMatch": "مورد قبلی (Shift+Enter)",
+ "nextMatch": "مورد بعدی (Enter)",
+ "lines": "{count} خط",
+ "stop": "توقف",
+ "start": "شروع",
+ "waitingForLogs": "در انتظار لاگ…",
+ "pressStart": "برای شروع استریم لاگ، شروع را بزنید",
+ "copiedToClipboard": "در کلیپبورد کپی شد",
+ "logsErrorTitle": "خطای لاگ",
+ "containerStoppedTitle": "کانتینر متوقف شد",
+ "containerExited": "کانتینر با کد {code} خارج شد",
+ "connectFailedTitle": "اتصال به لاگ ترمینال ناموفق بود",
+ "connectFailed": "اتصال به لاگ ترمینال ناموفق بود، مطمئن شوید پروژه در حال اجراست"
+ }
+ },
+ "services": {
+ "addModal": {
+ "addTitle": "افزودن اپ یا سرویس",
+ "configureTitle": "پیکربندی {name}",
+ "fallbackService": "اپ",
+ "pickSubtitle": "از کاتالوگ انتخاب کنید یا از ایمیج سفارشی استفاده کنید.",
+ "configureSubtitle": "نام، محیط و انتشار را تنظیم کنید.",
+ "backToCatalog": "بازگشت به کاتالوگ",
+ "close": "بستن",
+ "browse": "مرور",
+ "allServices": "همهٔ سرویسها",
+ "customImage": "ایمیج سفارشی",
+ "services": "سرویسها",
+ "serviceCountOne": "{count} سرویس",
+ "serviceCountOther": "{count} سرویس",
+ "searchPlaceholder": "جستجو بر اساس نام، ایمیج یا تگ…",
+ "cloudConnectHint": "برای مرور کاتالوگ سرویس ابری، Openship Cloud را وصل کنید. همچنان میتوانید هر ایمیجی را دستی اضافه کنید.",
+ "cloudConnectTitle": "اتصال Openship Cloud",
+ "cloudConnectBody": "این نمونه را به Openship Cloud وصل کنید تا ایمیجهای ابری مدیریتشده را مرور و اضافه کنید. همچنان میتوانید هر ایمیجی را دستی از ایمیجهای محلی اضافه کنید.",
+ "cloudConnectButton": "اتصال به Openship Cloud",
+ "noMatchesTitle": "نتیجهای نیست",
+ "noMatchesBody": "دسته یا عبارت دیگری را امتحان کنید — یا ایمیج سفارشی را از نوار کناری انتخاب کنید.",
+ "localImages": "ایمیجهای محلی",
+ "openshipCloud": "Openship Cloud",
+ "localDocker": "Docker محلی",
+ "customEntryName": "ایمیج سفارشی",
+ "customEntryDescription": "هر ایمیج عمومی یا خصوصی Docker را با تگ بکشید.",
+ "unnamed": "بدون نام",
+ "serviceName": "نام",
+ "ports": "پورتها",
+ "image": "ایمیج",
+ "persistentVolumes": "والیومهای پایدار",
+ "preSeeded": "از پیش برای این سرویس آماده شده",
+ "optional": "اختیاری",
+ "removeVolume": "حذف والیوم",
+ "addVolume": "افزودن والیوم",
+ "volumesHint": "والیومها داده را بین ریاستارتها نگه میدارند. نام سمت چپ، مسیر کانتینر سمت راست.",
+ "environmentVariables": "متغیرهای محیطی",
+ "cancel": "لغو",
+ "addService": "افزودن",
+ "serviceNameRequired": "نام لازم است.",
+ "imageRequired": "ایمیج لازم است.",
+ "addServiceFailed": "افزودن سرویس ناموفق بود",
+ "categories": {
+ "database": "پایگاهداده",
+ "cache": "کش",
+ "search": "جستجو",
+ "vector": "بردار و هوش مصنوعی",
+ "queue": "صف و استریم",
+ "storage": "ذخیرهسازی شیء",
+ "auth": "احراز هویت و هویت",
+ "mail": "ایمیل و SMTP",
+ "runtime": "زمان اجرا",
+ "other": "سایر"
+ }
+ },
+ "detail": {
+ "tabs": {
+ "overview": "نمای کلی",
+ "terminal": "ترمینال",
+ "logs": "لاگها",
+ "env": "محیط",
+ "settings": "تنظیمات",
+ "backup": "پشتیبان"
+ },
+ "status": {
+ "running": "در حال اجرا",
+ "stopped": "متوقف",
+ "disabled": "غیرفعال",
+ "failed": "ناموفق",
+ "starting": "در حال شروع",
+ "restarting": "در حال ریاستارت",
+ "unknown": "نامشخص"
+ },
+ "network": "شبکه",
+ "configuration": "پیکربندی",
+ "volumes": "والیومها",
+ "terminal": "ترمینال",
+ "backup": "پشتیبان",
+ "ports": "پورتها",
+ "hostPort": "پورت میزبان",
+ "containerIp": "IP کانتینر",
+ "internalAddress": "آدرس داخلی",
+ "internalAddressHint": "سرویسهای دیگر با این آدرس به این یکی میرسند.",
+ "currentIp": "IP فعلی",
+ "currentIpHint": "با ریاستارت عوض میشود — بهجایش از آدرس داخلی بالا استفاده کنید.",
+ "workspaceId": "شناسهٔ فضای کاری",
+ "containerId": "شناسهٔ کانتینر",
+ "restartPolicy": "سیاست ریاستارت",
+ "command": "دستور",
+ "dependsOn": "وابسته به",
+ "startShellHint": "سرویس را شروع کنید تا شل باز شود.",
+ "saveEnvironment": "ذخیرهٔ محیط",
+ "stop": "توقف",
+ "restart": "ریاستارت",
+ "start": "شروع",
+ "starting": "در حال شروع…",
+ "enableAndStart": "فعالسازی و شروع",
+ "redeploying": "در حال استقرار دوباره…",
+ "redeploy": "استقرار دوباره",
+ "disableService": "غیرفعال کردن سرویس",
+ "enableService": "فعال کردن سرویس",
+ "delete": "حذف",
+ "backupSubtitle": {
+ "manualOnly": "فقط دستی",
+ "cron": "cron {expr}",
+ "preDeploy": "پیش از استقرار",
+ "webhook": "وبهوک",
+ "none": "سیاست پشتیبانی برای این سرویس نیست"
+ },
+ "backupNow": "پشتیبان الان",
+ "editPolicy": "ویرایش سیاست",
+ "createPolicy": "ایجاد سیاست",
+ "deleteTitle": "حذف سرویس",
+ "deleteBody": "این «{name}» را از پروژه برمیدارد و کانتینر فعال و مسیرش را پاک میکند.",
+ "deleteCancel": "لغو",
+ "deleteConfirm": "حذف سرویس",
+ "toast": {
+ "envUpdated": "محیط بهروز شد",
+ "envSaveFailed": "ذخیرهٔ محیط ناموفق بود",
+ "deployFirstStart": "اول پروژه را مستقر کنید، سپس این سرویس شروع میشود",
+ "deployFailed": "استقرار ناموفق بود",
+ "serviceStarting": "{name} در حال شروع است",
+ "serviceTitle": "سرویس",
+ "deployFirstRedeploy": "اول پروژه را مستقر کنید، سپس میتوانید این سرویس را دوباره مستقر کنید",
+ "enableBeforeRedeploy": "قبل از استقرار دوباره، سرویس را فعال کنید",
+ "redeployFailed": "استقرار دوباره ناموفق بود",
+ "stillStarting": "هنوز در حال شروع است — برای پیشرفت لاگها را ببینید",
+ "serviceUpdated": "سرویس بهروز شد",
+ "updateFailed": "بهروزرسانی سرویس ناموفق بود",
+ "serviceDeleted": "سرویس حذف شد",
+ "deleteFailed": "حذف سرویس ناموفق بود",
+ "backupRunFailed": "اجرای پشتیبان ناموفق بود"
+ },
+ "duplicateContainers": "کانتینر دیگری هم با این سرویس همخوان است: {names}",
+ "editInSettings": "ویرایش در تنظیمات"
+ },
+ "settingsForm": {
+ "name": "نام",
+ "rootDirectory": "پوشهٔ ریشه",
+ "framework": "فریمورک",
+ "packageManager": "مدیر بسته",
+ "buildImage": "ایمیج ساخت",
+ "installCommand": "دستور نصب",
+ "buildCommand": "دستور ساخت",
+ "startCommand": "دستور شروع",
+ "outputDirectory": "پوشهٔ خروجی",
+ "image": "ایمیج",
+ "dockerfile": "Dockerfile",
+ "buildContext": "زمینهٔ ساخت",
+ "ports": "پورتها",
+ "dependsOn": "وابسته به",
+ "command": "دستور",
+ "restartPolicy": "سیاست ریاستارت",
+ "volumes": "والیومها",
+ "healthcheck": "بررسی سلامت",
+ "healthcheckHint": "دستور تست (فرم شل). خالی بگذارید تا بررسی پیشفرض ایمیج استفاده شود.",
+ "enabled": "فعال",
+ "enabledHint": "سرویسهای فعال همراه پروژه مستقر میشوند.",
+ "saveChanges": "ذخیرهٔ تغییرات",
+ "errors": {
+ "nameRequired": "نام سرویس لازم است.",
+ "imageOrDockerfile": "نام ایمیج، یا زمینهٔ ساخت / Dockerfile اضافه کنید.",
+ "buildContextOrDockerfile": "زمینهٔ ساخت یا مسیر Dockerfile اضافه کنید.",
+ "rootDirectory": "پوشهٔ ریشه اضافه کنید (مثلاً apps/web).",
+ "buildOrStart": "حداقل یک دستور ساخت یا دستور شروع اضافه کنید.",
+ "saveFailed": "ذخیرهٔ سرویس ناموفق بود."
+ },
+ "alias": "نام مستعار شبکه",
+ "aliasHint": "نام DNS اضافی که سرویسهای دیگر در کنار نام سرویس میتوانند برای رسیدن به این یکی استفاده کنند. برای پیشفرض خالی بگذارید.",
+ "portsHint": "پورت کانتینر، یا میزبان:کانتینر. برای افزودن هر کدام Enter بزنید.",
+ "volumesHint": "والیوم نامدار یا مسیر میزبان، نگاشت به مسیر کانتینر. برای افزودن هر کدام Enter بزنید.",
+ "dependsOnHint": "سرویسهایی که باید قبل از این یکی شروع شوند.",
+ "dependsOnEmpty": "هنوز سرویس دیگری در این پروژه نیست.",
+ "removeTag": "حذف",
+ "sections": {
+ "general": "عمومی",
+ "generalHint": "نام و اینکه آیا این سرویس همراه پروژه مستقر میشود.",
+ "source": "منبع",
+ "sourceHint": "یک ایمیج ازپیشساخته بکشید، یا از Dockerfile در مخزن بسازید.",
+ "sourceMonorepoHint": "چطور این اپ نصب، ساخته و شروع میشود.",
+ "networking": "شبکه",
+ "networkingHint": "پورتها، نام مستعار DNS داخلی، و ترتیب شروع.",
+ "runtime": "زمان اجرا",
+ "runtimeHint": "دستور راهاندازی، سیاست ریاستارت، و والیومهای متصل.",
+ "health": "سلامت و آمادگی",
+ "healthHint": "بررسی زندهبودن و دروازهٔ آمادگی هنگام استقرار."
+ },
+ "orBuildFromSource": "یا از منبع بساز",
+ "imageHint": "برای ساخت از Dockerfile پایین خالی بگذارید."
+ }
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/fa/projectSettings.json b/apps/dashboard/src/i18n/locales/fa/projectSettings.json
new file mode 100644
index 000000000..8ce5d4d4a
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/fa/projectSettings.json
@@ -0,0 +1,1025 @@
+{
+ "settingSection": {
+ "edit": "ویرایش",
+ "save": "ذخیره",
+ "cancel": "لغو"
+ },
+ "appSource": {
+ "title": "منبع",
+ "description": "این اپ از یک انتشار مستقر میشود — مخزنی برای push وجود ندارد؛ بهروزرسانیها بهصورت نسخههای جدید میآیند.",
+ "sourceType": "منبع",
+ "kindRelease": "انتشار",
+ "kindImage": "ایمیج کانتینر",
+ "current": "نسخهٔ فعلی",
+ "latest": "آخرین نسخه",
+ "repository": "مخزن",
+ "notChecked": "هنوز بررسی نشده",
+ "upToDate": "بهروز",
+ "updateAvailable": "بهروزرسانی موجود است",
+ "checkForUpdates": "بررسی بهروزرسانیها",
+ "checkFailed": "بررسی بهروزرسانیها ممکن نشد",
+ "update": "بهروزرسانی",
+ "updateTo": "بهروزرسانی به {version}",
+ "updating": "در حال بهروزرسانی…",
+ "updateFailed": "بهروزرسانی ناموفق بود",
+ "toastTitle": "منبع",
+ "cliManaged": "این صفحهٔ کنترل Openship است — خودش را بهروز میکند. برای ارتقا، «openship update» را روی میزبان اجرا کنید؛ اینجا چیزی برای تغییر نیست."
+ },
+ "appSettings": {
+ "modeApp": "تنظیمات اپ",
+ "modeDeployment": "استقرار",
+ "saveChanges": "ذخیرهٔ تغییرات",
+ "saving": "در حال ذخیره…",
+ "saved": "تنظیمات ذخیره شد",
+ "saveHint": "ذخیره کنید تا این تغییرات ثبت شوند — در استقرار بعدی اپ اعمال میشوند.",
+ "applyNow": "اعمال فوری",
+ "applying": "در حال اعمال…",
+ "applied": "در حال اعمال تغییرات",
+ "restartNote": "الان اعمال کنید تا اپ با تنظیمات جدید ریاستارت شود، یا در استقرار بعدی اعمال میشوند.",
+ "redeployNote": "اعمال این تغییرات اپ را از نو میسازد — ممکن است بعداً لازم باشد دوباره وارد داشبورد یا CLI آن شوید.",
+ "advancedShow": "نمایش پیشرفته",
+ "advancedHide": "پنهان کردن پیشرفته",
+ "secretSet": "تنظیم شده — برای نگه داشتن مقدار فعلی خالی بگذارید",
+ "loadError": "بارگذاری تنظیمات اپ ممکن نشد.",
+ "saveFailed": "ذخیرهٔ تنظیمات ممکن نشد.",
+ "applyFailed": "اعمال تغییرات ممکن نشد.",
+ "unmanaged": "این اپ هنوز تنظیمات قابل پیکربندی ندارد."
+ },
+ "appInstall": {
+ "install": "نصب",
+ "installing": "در حال نصب…",
+ "advanced": "گزینههای پیشرفتهٔ استقرار",
+ "nameLabel": "نام",
+ "nameHint": "نحوهٔ نمایش این اپ در Openship.",
+ "domainTitle": "کجا باید مستقر شود؟",
+ "domainFree": "زیردامنهٔ رایگان",
+ "domainFreeHint": "یک زیردامنهٔ رایگان روی {domain}",
+ "domainCustom": "دامنهٔ خودتان",
+ "domainCustomPlaceholder": "app.example.com",
+ "domainNone": "بدون URL عمومی",
+ "domainNoneHint": "فقط داخلی — بدون آدرس عمومی.",
+ "customRequired": "دامنهٔ خود را وارد کنید، یا گزینهٔ دیگری انتخاب کنید.",
+ "progressPreparing": "در حال آمادهسازی…",
+ "progressDeploying": "در حال استقرار…",
+ "progressLive": "اپ شما زنده است",
+ "openApp": "باز کردن اپ",
+ "goToApp": "رفتن به اپ",
+ "installFailed": "نصب ناموفق بود",
+ "installFailedBody": "قبل از اجرای کامل همه چیز متوقف شد. جزئیات فنی لاگ کامل ساخت را دارد.",
+ "installCancelledSelf": "شما این نصب را متوقف کردید.",
+ "installCancelledBody": "کانتینرها و ایمیجهایی که ساخته شده بودند حذف شدند و والیومهای داده نگه داشته شدند — نصب دوباره از آنها استفاده میکند. هر چیزی که از قبل زنده بود دستنخورده ماند.",
+ "settledAtPhase": "در {phase} متوقف شد",
+ "viewDetails": "مشاهدهٔ جزئیات فنی",
+ "showLogs": "نمایش لاگها",
+ "hideLogs": "پنهان کردن لاگها",
+ "deployLogs": "لاگهای استقرار",
+ "logsWaiting": "در انتظار خروجی…",
+ "logsEmpty": "خروجیای ثبت نشد.",
+ "back": "بازگشت",
+ "phaseImages": "آمادهسازی ایمیجها",
+ "phaseServices": "شروع سرویسها",
+ "phaseAppSetup": "تکمیل راهاندازی",
+ "phaseReady": "زنده",
+ "phaseQueued": "در صف…",
+ "stepperTitle": "مراحل نصب",
+ "firstLoginTitle": "اولین ورود",
+ "firstLoginUser": "نام کاربری",
+ "firstLoginPassword": "رمز عبور",
+ "destinationTitle": "محل نصب",
+ "destinationHint": "سرور یا کلادی را که این اپ روی آن اجرا میشود انتخاب کنید.",
+ "descMore": "بیشتر",
+ "descLess": "کمتر",
+ "needsTitle": "آنچه نیاز دارد",
+ "needsMemory": "{value} رم",
+ "needsCpu": "{value}",
+ "hostFitTitle": "این سرور از نیاز {app} کوچکتر است",
+ "hostFitMemory": "به {needed} RAM نیاز دارد — این یکی {available} دارد",
+ "hostFitCpu": "به {needed} نیاز دارد — این یکی {available} دارد",
+ "hostFitHint": "روی ماشین بزرگتری نصب کنید، یا مقصد دیگری انتخاب کنید.",
+ "routePortLabel": "فقط پورت (بدون دامنه)",
+ "routePortDesc": "از آدرس و پورت میزبان در دسترس است — دامنه یا کلاد لازم نیست.",
+ "routeFreeLabel": "زیردامنهٔ رایگان",
+ "routeFreeDesc": "یک زیردامنهٔ رایگان روی {domain}، مسیریابیشده توسط Openship Cloud.",
+ "routeFreeNeedsCloud": "برای استفاده از زیردامنهٔ رایگان، Openship Cloud را وصل کنید.",
+ "routeCustomLabel": "دامنهٔ خودتان",
+ "routeCustomDesc": "دامنهای که مالکتان است را به این اپ اشاره دهید.",
+ "routeDomainLabel": "دامنه",
+ "routeDomainDesc": "از طریق دامنه به آن برسید — زیردامنهٔ رایگان یا دامنهٔ خودتان.",
+ "exposeTitle": "انتشار",
+ "httpBadge": "وب",
+ "tcpBadge": "پایگاهداده",
+ "tcpPublishLabel": "انتشار پورت",
+ "tcpPublishDesc": "روی میزبان منتشر کنید تا ابزارهای خارجی بتوانند وصل شوند.",
+ "tcpInternalLabel": "فقط داخلی",
+ "tcpInternalDesc": "فقط سرویسهای دیگر همین اپ میتوانند به آن برسند.",
+ "tcpFirewallNote": "برای اتصال از بیرون، پورت {port} را در فایروال سرور باز کنید.",
+ "desktopReachNote": "روی دسکتاپ میتوانید آن را روی localhost باز کنید — یا برای سرور ریموت پورت را از سرورها → پورتها فوروارد کنید.",
+ "statusInstalling": "در حال نصب",
+ "statusLive": "زنده",
+ "statusCancelled": "لغو شده",
+ "statusFailed": "ناموفق",
+ "stopInstall": "توقف نصب",
+ "stopping": "در حال توقف…",
+ "installCancelled": "نصب لغو شد",
+ "startOver": "شروع دوباره",
+ "breadcrumbApps": "اپها",
+ "appEyebrow": "اپ",
+ "stopFailed": "توقف نصب ممکن نشد",
+ "progressStep": "گام {current} از {total}",
+ "progressServicesReady": "{done} از {total} سرویس آماده",
+ "summaryTitle": "پیکربندی",
+ "summaryDestination": "مقصد",
+ "summaryServices": "سرویسها",
+ "mail": {
+ "title": "راهاندازی ایمیل",
+ "subtitle": "وبمیل را روی دامنهٔ خودتان مستقر کنید، یا یک صندوق پستی موجود را وصل کنید.",
+ "chooseSelf": "میزبانی شخصی سرور ایمیل",
+ "chooseSelfDesc": "SMTP/IMAP کامل + وبمیل (iRedMail) روی سرور شما — همه چیز را ما فراهم میکنیم.",
+ "chooseConnect": "اتصال صندوق پستی موجود",
+ "chooseConnectDesc": "فقط رابط وبمیل را مستقر کنید، اشارهشده به هر ارائهدهندهٔ خارجی SMTP/IMAP — Amazon SES، Oracle Cloud، Gmail، Fastmail و بیشتر.",
+ "provider": "ارائهدهنده",
+ "presets": {
+ "custom": "سفارشی",
+ "ses": "Amazon SES",
+ "gmail": "Gmail",
+ "fastmail": "Fastmail"
+ },
+ "sendOnlyNote": "{provider} فقط ایمیل میفرستد — صندوق پستی نگه نمیدارد. IMAP باید به جایی اشاره کند که ایمیل واقعاً ذخیره شده است.",
+ "sendOnlyNoInbox": "هنوز صندوق ورودی ندارید؟ اینجا یک سرور ایمیل راهاندازی کنید، سپس ارسال را از طریق {provider} رله کنید →",
+ "imapOnOurServer": "صندوقهای پستی از {host} خوانده میشوند — سرور ایمیل خود این نمونه. فقط ارسال از طریق {provider} میرود.",
+ "relayNote": "این نمونه از قبل سرور ایمیل اجرا میکند. اگر صندوقها روی آن هستند، بهجای آن ارسال را از زبانهٔ تحویل → ارسال رله کنید: IMAP محلی میماند و فقط ارسال به {provider} میرود، بهازای دامنه یا آدرس، و وبمیل خودش به کار ادامه میدهد.",
+ "relayCta": "بهجای آن رله را پیکربندی کنید →",
+ "hostname": "آدرس وبمیل",
+ "hostnameHint": "URL عمومی که افراد برای خواندن ایمیل باز میکنند، مثلاً mail.example.com.",
+ "imapHost": "میزبان IMAP",
+ "imapPort": "پورت IMAP",
+ "smtpHost": "میزبان SMTP",
+ "smtpPort": "پورت SMTP",
+ "deploy": "استقرار وبمیل",
+ "deploying": "در حال استقرار…",
+ "hostnameRequired": "آدرس وبمیل را وارد کنید.",
+ "hostsRequired": "هم میزبان IMAP و هم میزبان SMTP را وارد کنید."
+ }
+ },
+ "advanced": {
+ "projectInfo": {
+ "title": "اطلاعات پروژه",
+ "description": "خلاصهٔ پیکربندی فعلی"
+ },
+ "metric": {
+ "status": "وضعیت",
+ "project": "پروژه",
+ "hostedOn": "میزبانیشده روی"
+ },
+ "projectMenu": {
+ "rename": "تغییر نام پروژه",
+ "copyId": "کپی شناسهٔ پروژه",
+ "idCopied": "شناسهٔ پروژه کپی شد",
+ "copyFailed": "کپی به کلیپبورد ممکن نشد",
+ "pause": "توقف پروژه",
+ "resume": "ادامهٔ پروژه",
+ "delete": "حذف پروژه"
+ },
+ "rename": {
+ "title": "تغییر نام پروژه",
+ "description": "نام نمایشی را تغییر میدهد. چیزی دوباره مستقر نمیشود.",
+ "label": "نام پروژه",
+ "placeholder": "پروژهٔ من",
+ "identityNote": "پروژه هویت «{slug}» را نگه میدارد — URL، نام کانتینرها و والیومها عوض نمیشوند. برای تغییر URL، دامنه را در دامنهها و مسیرها ویرایش کنید.",
+ "save": "ذخیره",
+ "cancel": "لغو",
+ "toast": {
+ "renamed": "به {name} تغییر نام یافت",
+ "failed": "تغییر نام پروژه ناموفق بود"
+ }
+ },
+ "statusActive": "فعال",
+ "statusDisabled": "غیرفعال",
+ "hostedCloud": "Openship Cloud",
+ "hostedServer": "سرور",
+ "hostedLocal": "محلی",
+ "projectStatus": {
+ "title": "وضعیت پروژه",
+ "description": "کنترل کنید پروژه زنده باشد یا متوقف",
+ "active": "پروژه فعال",
+ "disabled": "پروژه غیرفعال",
+ "liveAccessible": "زنده و در دسترس",
+ "pausedInaccessible": "متوقف و غیرقابل دسترسی",
+ "disable": "غیرفعال کردن",
+ "enable": "فعال کردن",
+ "migrating": "در حال مهاجرت",
+ "migratingHint": "در حال انتقال به سرور دیگر"
+ },
+ "migration": {
+ "title": "مهاجرت",
+ "description": "این پروژه را به سرور دیگری منتقل کنید",
+ "currentServer": "سرور فعلی",
+ "migrate": "مهاجرت",
+ "noOtherServers": "سرور دومی اضافه کنید تا این پروژه را به آن منتقل کنید.",
+ "cloudLater": "سرور به سرور. انتقال به یا از Openship Cloud هنوز پشتیبانی نمیشود.",
+ "startFailed": "شروع مهاجرت ممکن نشد",
+ "confirmTitle": "انتقال {project} به {server}",
+ "confirmSubtitle": "تا وقتی انتقال را شروع نکنید چیزی عوض نمیشود.",
+ "svcVolumes": "{count} والیوم",
+ "downtimeNote": "پروژه تا وقتی دادهها کپی شوند و سرور جدید آن را شروع کند آفلاین است. سرویسهای دارای حالت در کل انتقال خاموش میمانند.",
+ "dnsNote": "DNS هنوز به سرور قدیمی اشاره میکند — وقتی سرور جدید سرویس میدهد رکوردها را جابهجا کنید.",
+ "cancel": "لغو",
+ "confirmStart": "شروع انتقال",
+ "duplicate": "تکثیر",
+ "intentMove": "انتقال",
+ "intentCopy": "تکثیر",
+ "intentMoveHint": "پروژه منتقل میشود. پس از تأیید، کانتینرها و والیومهایش از سرور قدیمی حذف میشوند.",
+ "intentCopyHint": "اصلی به اجرا ادامه میدهد. پروژهٔ دومی روی سرور جدید با کپی داده ساخته میشود.",
+ "confirmTitleCopy": "تکثیر {project} روی {server}",
+ "confirmStartCopy": "شروع تکثیر",
+ "downtimeNoteCopy": "پروژه برای کپی تمیز داده کمی آفلاین میشود — والیومها باید ثابت بمانند. بهمحض اتمام کپی، دوباره روی سرور قدیمی شروع میشود.",
+ "copyNoDomainsNote": "کپی بدون دامنه شروع میشود — دامنههای اصلی با پروژهای که آنها را سرو میکند میمانند.",
+ "fromLabel": "از",
+ "toLabel": "به",
+ "modeLabel": "حالت",
+ "scopeLabel": "چه چیزی تکثیر شود",
+ "scopeAll": "انتخاب همه",
+ "scopeHintAll": "هر سرویس، همراه دادهاش.",
+ "scopeHintSome": "{count} سرویس — بقیه فقط روی اصلی میمانند.",
+ "sourceServices": "{count} سرویس",
+ "runningStatus": "در حال انجام — {status}"
+ },
+ "cache": {
+ "title": "کش",
+ "description": "پاک کردن آرتیفکتهای ساخت کششده و نصب وابستگیها",
+ "clearInstall": "پاک کردن کش نصب",
+ "clearInstallDesc": "حذف node_modules کششده",
+ "clearBuild": "پاک کردن کش ساخت",
+ "clearBuildDesc": "حذف خروجی ساخت کششده"
+ },
+ "transfer": {
+ "title": "انتقال و کلون",
+ "description": "این پروژه را به محیط دیگری منتقل یا تکثیر کنید",
+ "current": "فعلی",
+ "transfer": "انتقال",
+ "clone": "کلون",
+ "anotherServer": "سرور دیگر",
+ "targetLocal": "دستگاه محلی",
+ "targetServer": "سرور خودمیزبان",
+ "targetCloud": "Openship Cloud"
+ },
+ "danger": {
+ "title": "منطقهٔ خطر",
+ "description": "اقدامات برگشتناپذیر برای این پروژه",
+ "body": "حذف این پروژه همهٔ استقرارها، دامنههای سفارشی، متغیرهای محیط و دادههای تحلیل را برای همیشه پاک میکند.",
+ "delete": "حذف پروژه"
+ },
+ "toast": {
+ "toggleFailed": "تغییر وضعیت پروژه ناموفق بود",
+ "clearInstallFailed": "پاک کردن کش نصب ناموفق بود",
+ "clearBuildFailed": "پاک کردن کش ساخت ناموفق بود"
+ },
+ "routing": {
+ "title": "مسیریابی",
+ "description": "لبه چگونه به آپاستریم این اپ میرسد",
+ "intro": "انتخاب کنید لبه چگونه کانتینر این پروژه را آدرسدهی کند.",
+ "note": "پورت لوپبک توصیه میشود: پورت خصوصی میزبان روی 127.0.0.1، پایدار بین ریاستارتها و هرگز در معرض شبکه نیست. IP کانتینر پیشرفته است (جابهجایی بدون قطعی؛ لبه باید روی بریج Docker باشد؛ روی Docker Desktop پشتیبانی نمیشود).",
+ "modes": {
+ "auto": {
+ "label": "خودکار",
+ "desc": "توصیهشده (پورت لوپبک)"
+ },
+ "loopbackPort": {
+ "label": "پورت لوپبک",
+ "desc": "پورت خصوصی میزبان روی 127.0.0.1"
+ },
+ "containerIp": {
+ "label": "IP کانتینر",
+ "desc": "پیشرفته — IP بریج"
+ }
+ },
+ "toast": {
+ "saved": "مسیریابی روی {mode} تنظیم شد",
+ "failed": "بهروزرسانی مسیریابی ناموفق بود"
+ }
+ },
+ "internalAlias": {
+ "title": "نام میزبان داخلی",
+ "description": "نام سفارشی که سرویسهای دیگر برای دسترسی خصوصی به این اپ استفاده میکنند",
+ "intro": "یک نام DNS اضافه برای این اپ روی شبکهٔ خصوصی پروژهاش تنظیم کنید. در کنار پیشفرض (اسلاگ پروژه) حل میشود — سرویسهای لینکشده میتوانند از هر کدام استفاده کنند. دسترسی عمومی عوض نمیشود؛ این هرگز پورتی را منتشر نمیکند.",
+ "label": "نام مستعار میزبان",
+ "hint": "فقط حروف، ارقام و خط تیره. برای استفاده از اسلاگ پروژه خالی بگذارید.",
+ "save": "ذخیره",
+ "toast": {
+ "saved": "نام میزبان داخلی بهروز شد",
+ "failed": "بهروزرسانی نام میزبان داخلی ممکن نشد",
+ "invalid": "حداقل یک حرف یا رقم وارد کنید"
+ }
+ }
+ },
+ "routeRules": {
+ "title": "قوانین مسیر",
+ "description": "محدودیت نرخ، مسدودسازی، و قوانین اجازه/رد که در لبه اعمال میشوند — فوری، بدون استقرار دوباره.",
+ "loading": "در حال بارگذاری قوانین…",
+ "empty": "هنوز قانون مسیری نیست. یکی در پایین اضافه کنید.",
+ "allHosts": "همهٔ نامهای میزبان",
+ "allPaths": "همهٔ مسیرها",
+ "addTitle": "افزودن قانون",
+ "pathLabel": "پیشوند مسیر (اختیاری)",
+ "rps": "درخواست در ثانیه",
+ "burst": "انفجار",
+ "methods": "فقط این متدها",
+ "banIps": "مسدودسازی IP / CIDR",
+ "banCountries": "مسدودسازی کشورها (ISO)",
+ "allowCountries": "فقط این کشورها (ISO)",
+ "denyCidrs": "رد CIDRها",
+ "allowReferers": "هاتلینک: refererهای مجاز",
+ "banUserAgents": "مسدودسازی user-agent (زیررشته)",
+ "blockEmptyUa": "مسدود کردن user-agent خالی",
+ "blockStatus": "کد پاسخ مسدودسازی",
+ "add": "افزودن قانون",
+ "remove": "حذف",
+ "enabled": "فعال",
+ "disabled": "غیرفعال",
+ "saved": "قانون ذخیره شد",
+ "saveFailed": "ذخیرهٔ قانون ناموفق بود",
+ "loadFailed": "بارگذاری قوانین ناموفق بود"
+ },
+ "backup": {
+ "live": {
+ "title": "پشتیبان زنده",
+ "description": "پیشرفت لحظهای برای اجرایی که همین الان شروع کردید. با تازهسازی صفحه باقی میماند.",
+ "dismiss": "بستن"
+ },
+ "destinations": {
+ "title": "مقصدهای پشتیبان",
+ "description": "مقصدهای ذخیرهسازی بهازای هر کاربر. باکتهای سازگار با S3، سرورهای SFTP یا دیسکهای محلی اضافه کنید.",
+ "manage": "مدیریت",
+ "loading": "در حال بارگذاری…",
+ "emptyPrefix": "هنوز مقصدی نیست. ",
+ "emptyLink": "یکی بسازید",
+ "emptySuffix": " تا پشتیبانگیری شروع شود.",
+ "verified": "تأییدشده",
+ "failed": "ناموفق"
+ },
+ "services": {
+ "title": "سرویسها + سیاستها",
+ "description": "هر سرویس میتواند سیاست پشتیبان خودش را داشته باشد (زمانبندی + مقصد). پیشفرضهای سطح پروژه در Chunk 2 میآید.",
+ "refresh": "تازهسازی",
+ "empty": "این پروژه هنوز سرویسی ندارد. پشتیبانهای سطح سرویس پس از افزودن اینجا ظاهر میشوند.",
+ "policyLabel": "سیاست:",
+ "manualOnly": "فقط دستی",
+ "preDeploy": "پیش از استقرار",
+ "webhook": "وبهوک",
+ "noPolicy": "بدون سیاست",
+ "backupNow": "پشتیبان همین حالا",
+ "editPolicy": "ویرایش سیاست",
+ "addDestinationFirst": "ابتدا یک مقصد اضافه کنید",
+ "createPolicyHint": "یک سیاست پشتیبان برای این سرویس بسازید",
+ "createPolicy": "ساخت سیاست"
+ },
+ "recent": {
+ "title": "پشتیبانهای اخیر",
+ "description": "۲۵ اجرای اخیر در همهٔ سرویسهای این پروژه.",
+ "loading": "در حال بارگذاری…",
+ "empty": "هنوز پشتیبانی نیست.",
+ "protectedTitle": "محافظتشده در برابر هرس نگهداری",
+ "protected": "محافظتشده",
+ "restoreTitle": "بازیابی از این پشتیبان",
+ "allowPrune": "اجازهٔ هرس نگهداری",
+ "protectFrom": "محافظت در برابر نگهداری"
+ },
+ "toast": {
+ "runFailed": "اجرای پشتیبان ناموفق بود",
+ "toggleProtectionFailed": "تغییر وضعیت محافظت ناموفق بود"
+ }
+ },
+ "build": {
+ "edit": "ویرایش",
+ "webmail": {
+ "title": "مدیریتشده توسط Openship",
+ "description": "وبمیل از یک خط لولهٔ ساخت و شروع ثابت استفاده میکند — فرمانهای نصب، ساخت و اجرا قابل پیکربندی نیستند. برای دریافت تغییرات بالادستی، از نمای کلی ایمیل دوباره مستقر کنید."
+ },
+ "services": {
+ "title": "تنظیمات بهازای هر سرویس",
+ "descriptionTemplate": "این پروژه {serviceLabel} دارد. فرمانهای ساخت، فریمورک، پورتها و فرمانهای اجرا روی هر ردیف سرویس هستند — آنها را در زبانهٔ سرویسها ببینید و ویرایش کنید.",
+ "open": "باز کردن سرویسها",
+ "subAppOne": "{count} زیر-اپ",
+ "subAppOther": "{count} زیر-اپ",
+ "composeOne": "{count} سرویس compose",
+ "composeOther": "{count} سرویس compose",
+ "both": "{subApps} و {composeServices}"
+ },
+ "runtime": {
+ "title": "پیکربندی زمان اجرا",
+ "description": "فقط خواندنی. از طریق ویزارد استقرار ویرایش کنید تا هر تغییر بهصورت نسخهٔ جدید ارسال شود.",
+ "framework": "فریمورک",
+ "packageManager": "مدیر بسته",
+ "runtimeIsolation": "ایزولهسازی زمان اجرا",
+ "runtimePort": "پورت زمان اجرا",
+ "installCommand": "فرمان نصب",
+ "buildCommand": "فرمان ساخت",
+ "noBuildStep": "بدون گام ساخت",
+ "outputDirectory": "پوشهٔ خروجی",
+ "rootDirectory": "پوشهٔ ریشه",
+ "composePath": "فایل Compose",
+ "startCommand": "فرمان شروع",
+ "staticNoServer": "استاتیک (بدون سرور)",
+ "modeSandboxed": "سندباکس (کانتینر)",
+ "modeDirect": "مستقیم (فرایند میزبان)",
+ "modeDefault": "پیشفرض (در استقرار تعیین میشود)",
+ "modeStatic": "هیچ — بهصورت فایلهای استاتیک سرو میشود"
+ },
+ "storage": {
+ "title": "ذخیرهسازی پایدار",
+ "description": "مسیرهایی که وقتی نسخهٔ جدید جایگزین قبلی میشود نگه داشته میشوند. بقیهٔ محتوای کانتینر هنگام استقرار دور ریخته میشود.",
+ "edit": "ویرایش",
+ "save": "ذخیره",
+ "cancel": "لغو",
+ "saved": "ذخیرهسازی بهروز شد. در استقرار بعدی اعمال میشود.",
+ "reset": "استفاده از پیشفرضهای فریمورک",
+ "empty": "بین استقرارها چیزی نگه داشته نمیشود.",
+ "inherited": "پیشفرضهای فریمورک برای این استک.",
+ "declared": "برای این پروژه تنظیم شده.",
+ "placeholder": "storage\nuploads:/app/public/uploads",
+ "hint": "هر خط یکی. مسیر ساده نسبت به اپ است (storage)، یا یک مانت کامل بدهید (name:/app/path، /host/path:/app/path)."
+ },
+ "objectStorage": {
+ "title": "ذخیرهسازی شیء",
+ "description": "یک باکت S3 برای آپلودها — از استقرار دوباره جان سالم به در میبرد و برخلاف مسیر محلی با بیش از یک رپلیکا کار میکند.",
+ "connect": "اتصال",
+ "disconnect": "قطع اتصال",
+ "disconnected": "ذخیرهسازی شیء قطع شد.",
+ "connected": "باکت تأیید و متصل شد. در استقرار بعدی اعمال میشود.",
+ "loading": "در حال بررسی…",
+ "empty": "باکتی متصل نیست. آپلودها روی دیسک کانتینر میمانند.",
+ "sourceLabel": "منبع",
+ "externalOption": "ارائهدهندهٔ خارجی",
+ "providerLabel": "ارائهدهنده",
+ "bucketLabel": "باکت",
+ "endpointLabel": "نقطهٔ پایانی",
+ "regionLabel": "منطقه",
+ "accessKeyLabel": "شناسهٔ کلید دسترسی",
+ "secretKeyLabel": "کلید دسترسی مخفی",
+ "testAndConnect": "آزمایش و اتصال",
+ "cancel": "لغو",
+ "envLabel": "متغیرها",
+ "envHint": "تنظیم میکند:",
+ "appliesOnDeploy": "در استقرار بعدی اعمال میشود."
+ },
+ "env": {
+ "title": "متغیرهای محیط",
+ "description": "رمزنگاریشده در حالت سکون. رازها ماسک میمانند — فقط مقادیری که تغییر میدهید نوشته میشوند.",
+ "edit": "ویرایش"
+ }
+ },
+ "envVars": {
+ "cancel": "لغو",
+ "saveChanges": "ذخیرهٔ تغییرات",
+ "close": "بستن",
+ "applyTitle": "اعمال بدون بازسازی",
+ "applyText": "ذخیره شد. اینها را روی سرویس در حال اجرا اعمال کنید — با env جدید ریاستارت میشود، بدون کلون یا بازسازی.",
+ "applyButton": "اعمال (ریاستارت، بدون بازسازی)",
+ "title": "متغیرهای محیط",
+ "description": "تولید. رمزنگاریشده در حالت سکون. رازها بهصورت تنظیمشده نشان داده میشوند — برای جایگزینی تایپ کنید؛ برای نگه داشتن خالی بگذارید.",
+ "empty": "هنوز متغیر محیطی نیست.",
+ "keyPlaceholder": "KEY",
+ "secretPlaceholder": "•••••••• (تنظیم شده — برای جایگزینی تایپ کنید)",
+ "valuePlaceholder": "مقدار",
+ "hideValue": "پنهان کردن مقدار",
+ "showValue": "نمایش مقدار",
+ "markedSecret": "علامتخورده بهعنوان راز",
+ "markSecret": "علامت بهعنوان راز",
+ "removeVariable": "حذف متغیر",
+ "addVariable": "افزودن متغیر",
+ "toast": {
+ "loadFailed": "بارگذاری متغیرهای محیط ناموفق بود",
+ "loadFailedTitle": "خطا",
+ "validationTitle": "اعتبارسنجی",
+ "saved": "متغیرهای محیط ذخیره شد",
+ "savedTitle": "ذخیره شد",
+ "saveFailed": "ذخیرهٔ متغیرهای محیط ناموفق بود",
+ "saveFailedTitle": "ذخیره ناموفق بود",
+ "noChanges": "تغییری برای ذخیره نیست — مقادیر ذخیرهشده نگه داشته شدند.",
+ "noChangesTitle": "چیزی برای ذخیره نیست",
+ "applying": "ریاستارت سرویس با env جدید — بدون بازسازی.",
+ "applyingTitle": "در حال اعمال",
+ "applyFailed": "اعمال env روی سرویس در حال اجرا ممکن نشد",
+ "applyFailedTitle": "اعمال ناموفق بود"
+ },
+ "linkedBadge": "لینکشده",
+ "linkedManaged": "از طریق {app}",
+ "linkedTitle": "یکپارچگی لینکشده",
+ "linkedBody": "این مقدار از اتصال {app} میآید. آن را از آن اپ مدیریت کنید — تغییرات اینجا ماندگار نمیشوند.",
+ "unlink": "حذف اتصال"
+ },
+ "gitInfo": {
+ "repository": "مخزن",
+ "autoDeploy": "استقرار خودکار",
+ "deploysOnPush": "با هر push مستقر میشود",
+ "manualOnly": "فقط استقرار دستی",
+ "branchToDeploy": "شاخه برای استقرار",
+ "chooseBranch": "انتخاب کنید از کدام شاخهٔ مخزن مستقر شود",
+ "save": "ذخیره",
+ "cancel": "لغو",
+ "viewOnGithub": "مشاهده در GitHub",
+ "recentCommits": "کامیتهای اخیر",
+ "totalCommits": "{count} در مجموع",
+ "noCommits": "هنوز کامیتی نیست",
+ "toast": {
+ "fetchBranchesFailed": "دریافت شاخهها ناموفق بود",
+ "errorTitle": "خطا",
+ "branchUpdated": "شاخه با موفقیت بهروز شد",
+ "updatedTitle": "بهروز شد",
+ "branchUpdateFailed": "بهروزرسانی شاخه ناموفق بود",
+ "autoDeployEnabled": "استقرار خودکار فعال شد",
+ "autoDeployDisabled": "استقرار خودکار غیرفعال شد",
+ "autoDeployFailed": "بهروزرسانی استقرار خودکار ناموفق بود"
+ }
+ },
+ "git": {
+ "connectFirst": {
+ "title": "ابتدا GitHub را وصل کنید",
+ "description": "حساب GitHub خود را لینک کنید تا مخزنی به این پروژه وصل شود.",
+ "connecting": "در حال اتصال…",
+ "connect": "اتصال GitHub"
+ },
+ "link": {
+ "title": "لینک کردن مخزن",
+ "description": "یک مخزن GitHub وصل کنید تا استقرارهای تحریکشده با وبهوک فعال شود.",
+ "select": "انتخاب مخزن"
+ },
+ "picker": {
+ "title": "انتخاب مخزن",
+ "description": "مخزنی را برای لینک به این پروژه انتخاب کنید",
+ "linking": "در حال لینک مخزن…"
+ },
+ "appBanner": {
+ "title": "GitHub App نصب نشده",
+ "description": "Openship GitHub App را روی حساب یا سازمان خود نصب کنید تا استقرار خودکار و استقرارهای تحریکشده با وبهوک برای پروژههای کلاد فعال شود.",
+ "install": "نصب GitHub App"
+ },
+ "webhookBanner": {
+ "title": "فعالسازی استقرار خودکار",
+ "description": "استقرار خودکار به یک نقطهٔ پایانی وبهوک مستقیم نیاز دارد. این API Openship را روی یک URL عمومی منتشر کنید یا دامنهٔ وبهوک تأییدشدهای برای تحویل مستقیم پیکربندی کنید."
+ },
+ "source": {
+ "title": "مخزن منبع",
+ "description": "یکپارچگی Git، محرکهای استقرار و سلامت مخزن",
+ "repository": "مخزن",
+ "open": "باز کردن"
+ },
+ "webhookEndpoint": {
+ "title": "نقطهٔ پایانی وبهوک",
+ "descriptionSet": "GitHub رویدادهای push را مستقیماً به این دامنه میفرستد",
+ "descriptionUnset": "دامنهای برای تحویل مستقیم وبهوک انتخاب کنید",
+ "updating": "در حال بهروزرسانی...",
+ "select": "انتخاب دامنه...",
+ "clear": "پاک کردن انتخاب"
+ },
+ "autoDeployCard": {
+ "title": "استقرار خودکار",
+ "enabled": "فعال",
+ "disabled": "غیرفعال",
+ "descDelivers": "Pushها به {domain} تحویل میشوند",
+ "descAuto": "Pushها استقرار را بهصورت خودکار شروع میکنند",
+ "descManual": "استقرارها باید دستی شروع شوند"
+ },
+ "webhookCard": {
+ "title": "وبهوک",
+ "direct": "مستقیم",
+ "active": "فعال",
+ "inactive": "غیرفعال",
+ "descDirect": "رویدادها مستقیماً از طریق {domain} تحویل میشوند",
+ "descActive": "رویدادهای مخزن به Openship میرسند",
+ "descInactive": "وبهوک پیکربندی نشده یا پاسخ نمیدهد"
+ },
+ "rollbackStrategy": {
+ "title": "استراتژی بازگردانی",
+ "rebuild": "بازساخت (git)",
+ "instant": "فوری (اسنپشات)",
+ "descRebuild": "کامیت قدیمی را دوباره میسازد، مگر ایمیجش هنوز روی میزبان باشد.",
+ "descInstant": "ایمیج هر نسخه را نگه میدارد تا بازیابی هرگز بازسازی نکند. دیسک بیشتری مصرف میکند.",
+ "toggleAria": "تغییر نگهداری اسنپشات",
+ "keepNone": "هیچ کپی اضافهای نگه ندار",
+ "keepCopies": "یک کپی بهازای هر نسخه نگه دار",
+ "descRebuildFiles": "کامیت قدیمی را دوباره میسازد، مگر فایلهایش هنوز روی میزبان باشد.",
+ "descInstantFiles": "فایلهای هر نسخه را نگه میدارد تا بازیابی هرگز بازسازی نکند. دیسک بیشتری مصرف میکند."
+ },
+ "rollbackHistory": {
+ "title": "تاریخچهٔ بازگردانی",
+ "valueOne": "{count} نسخه",
+ "valueOther": "{count} نسخه",
+ "descSnapshot": "نسخههای قدیمیتر برای آزاد کردن دیسک هرس میشوند.",
+ "descGit": "ایمیجهای اخیر در هر حال نگه داشته میشوند تا بازگردانی بتواند ساخت را رد کند.",
+ "decreaseAria": "کاهش تاریخچهٔ بازگردانی",
+ "increaseAria": "افزایش تاریخچهٔ بازگردانی",
+ "sizeEach": "حدود {size} هر کدام",
+ "diskFree": "{size} آزاد",
+ "autoBadge": "اندازهٔ خودکار",
+ "manualBadge": "تنظیم دستی",
+ "kindFiles": "فایلهای ساختهشده روی دیسک",
+ "kindImages": "ایمیجها روی میزبان",
+ "descGitFiles": "فایلهای نسخههای اخیر در هر حال نگه داشته میشوند تا بازگردانی بتواند ساخت را رد کند."
+ },
+ "commits": {
+ "title": "کامیتهای اخیر",
+ "subtitle": "آخرین کامیتها روی {branch}",
+ "empty": "کامیتی پیدا نشد",
+ "emptyDesc": "به مخزن push کنید تا فعالیت کامیتهای اخیر اینجا ظاهر شود.",
+ "viewAll": "مشاهدهٔ همهٔ کامیتها در GitHub"
+ },
+ "cloneToken": {
+ "title": "بازنویسی توکن کلون",
+ "description": "توکن محدود به پروژه که ماژول کلون قبل از برگشت به توکن سراسری یا GitHub App استفاده میکند",
+ "loading": "در حال بارگذاری…",
+ "explainer": "یک GitHub PAT را اینجا بچسبانید تا فقط برای این پروژه استفاده شود. بالاترین اولویت در زنجیرهٔ کلون — توکن سطح تنظیمات و GitHub App را لغو میکند.",
+ "hide": "پنهان کردن توکن",
+ "show": "نمایش توکن",
+ "saveToken": "ذخیرهٔ توکن",
+ "cancel": "لغو",
+ "savedTitle": "توکن برای این پروژه ذخیره شد",
+ "lastUpdated": "آخرین بهروزرسانی {when}",
+ "justNow": "همین الان",
+ "replace": "جایگزینی",
+ "clear": "پاک کردن"
+ },
+ "toast": {
+ "pasteToken": "قبل از ذخیره یک توکن بچسبانید",
+ "cloneTokenTitle": "توکن کلون",
+ "tokenSaved": "توکن کلون پروژه ذخیره شد",
+ "tokenSaveFailed": "ذخیرهٔ توکن ناموفق بود",
+ "tokenCleared": "توکن کلون پروژه پاک شد",
+ "tokenClearFailed": "پاک کردن توکن ناموفق بود",
+ "autoDeployEnabled": "استقرار خودکار فعال شد",
+ "autoDeployDisabled": "استقرار خودکار غیرفعال شد",
+ "autoDeployFailed": "تغییر وضعیت استقرار خودکار ناموفق بود",
+ "rollbackGit": "استراتژی بازگردانی: مبتنی بر Git (بدون اسنپشات)",
+ "rollbackSnapshot": "استراتژی بازگردانی: اسنپشات (آرتیفکتها نگه داشته میشوند)",
+ "rollbackStrategyFailed": "بهروزرسانی استراتژی بازگردانی ناموفق بود",
+ "rollbackWindowOne": "نگه داشتن آخرین {count} نسخه برای بازگردانی",
+ "rollbackWindowOther": "نگه داشتن آخرین {count} نسخه برای بازگردانی",
+ "rollbackHistoryFailed": "بهروزرسانی تاریخچهٔ بازگردانی ناموفق بود",
+ "webhookDomainSet": "دامنهٔ وبهوک روی {domain} تنظیم شد",
+ "webhookDomainCleared": "دامنهٔ وبهوک پاک شد",
+ "webhookDomainFailed": "تنظیم دامنهٔ وبهوک ناموفق بود",
+ "linked": "لینک شد به {repo}",
+ "appNotInstalled": "GitHub App برای این حساب نصب نشده",
+ "linkFailed": "لینک مخزن ناموفق بود"
+ }
+ },
+ "logs": {
+ "noRuntime": "لاگ زمان اجرا نیست",
+ "noRuntimeDesc": "این پروژه فرایند سرور در حال اجرا و دامنهای ندارد، پس چیزی برای استریم نیست. یک دامنه اضافه کنید تا لاگ درخواستهای لبه اینجا ظاهر شود.",
+ "terminal": "ترمینال",
+ "server": "سرور",
+ "requests": "درخواستها",
+ "target": "هدف لاگ زمان اجرا",
+ "targetDescProject": "بین زمان اجرای پروژه و زمان اجرای سرویسها جابهجا شوید.",
+ "targetDescService": "بین زمان اجرای سرویسها جابهجا شوید.",
+ "projectRuntime": "زمان اجرای پروژه",
+ "selectService": "سرویسی را برای دیدن لاگ زمان اجرا انتخاب کنید.",
+ "projectFallback": "پروژه"
+ },
+ "resources": {
+ "title": "قدرت ماشین",
+ "description": "محدودیتهای CPU و حافظه برای کانتینرهای این پروژه.",
+ "loading": "در حال خواندن ماشین مقصد…",
+ "machineCapacity": "ماشین: {cpu} vCPU · {memory}",
+ "exceedsMachine": "از این ماشین بیشتر است",
+ "appliesOnNextDeploy": "محدودیتها وقتی کانتینر از نو ساخته شود اعمال میشوند — برای اثرگذاری تغییر، دوباره مستقر کنید.",
+ "tiers": {
+ "unlimited": {
+ "name": "بدون محدودیت",
+ "description": "استفاده از کل ماشین (پیشفرض)",
+ "spec": "ظرفیت ماشین"
+ },
+ "micro": {
+ "name": "میکرو",
+ "description": "سایدکارها و ورکرهای خیلی کوچک"
+ },
+ "low": {
+ "name": "کم",
+ "description": "اپهای کوچک و سایتهای استاتیک"
+ },
+ "medium": {
+ "name": "متوسط",
+ "description": "بیشتر برنامههای وب"
+ },
+ "high": {
+ "name": "زیاد",
+ "description": "اپهای پرترافیک"
+ },
+ "xlarge": {
+ "name": "خیلی بزرگ",
+ "description": "پایگاهداده و بارهای سنگین"
+ }
+ },
+ "custom": {
+ "name": "سفارشی",
+ "description": "محدودیتهای خودتان را تنظیم کنید",
+ "notSet": "تنظیم نشده"
+ },
+ "customPanel": {
+ "boundedBy": "این ماشین {cpu} vCPU و {memory} دارد. برای بدون محدودیت ۰ وارد کنید.",
+ "capacityUnknown": "ظرفیت این ماشین خوانده نشد، پس سقفی اعمال نمیشود. برای بدون محدودیت ۰ وارد کنید.",
+ "cpuCores": "هستههای CPU",
+ "memory": "حافظه (MB)",
+ "zeroMeansNoLimit": "۰ = بدون محدودیت",
+ "cancel": "لغو",
+ "saving": "در حال ذخیره…",
+ "save": "ذخیره"
+ },
+ "toast": {
+ "updated": "قدرت ماشین بهروز شد",
+ "updateFailed": "بهروزرسانی قدرت ماشین ناموفق بود",
+ "invalidValues": "CPU و حافظه باید ۰ یا عددی مثبت باشند"
+ }
+ },
+ "sleep": {
+ "title": "حالت ماشین",
+ "description": "کنترل دسترسپذیری و کارایی هزینه",
+ "autoSleep": "خواب خودکار",
+ "recommended": "پیشنهادی",
+ "autoSleepDesc": "در بیکاری متوقف میشود، فوری بیدار میشود",
+ "autoSleepMeta": "بهصرفه • بدون شروع سرد",
+ "alwaysOn": "همیشه روشن",
+ "alwaysOnDesc": "کانتینر هرگز متوقف نمیشود",
+ "alwaysOnMeta": "حداکثر دسترسپذیری • هزینهٔ بالاتر",
+ "infoTitle": "خواب خودکار چگونه کار میکند",
+ "infoBefore": "وقتی درخواست هست، کانتینر شما در حال اجرا میماند. پس از بیفعالیتی برای صرفهجویی میخوابد. با درخواست بعدی ",
+ "infoEmphasis": "فوری",
+ "infoAfter": " بیدار میشود — بدون تأخیر شروع سرد.",
+ "toast": {
+ "updated": "حالت خواب با موفقیت بهروز شد",
+ "updateFailed": "بهروزرسانی حالت خواب ناموفق بود"
+ }
+ },
+ "deleteDialog": {
+ "title": "حذف پروژه",
+ "bodyPrefix": "مطمئنید میخواهید ",
+ "bodySuffix": " را حذف کنید؟ این کار برگشتناپذیر است و همهٔ داده، استقرارها و تنظیمات پروژه را برای همیشه حذف میکند.",
+ "cancel": "لغو",
+ "delete": "حذف پروژه"
+ },
+ "deletion": {
+ "title": "حذف پروژه",
+ "cannotUndo": "این کار برگشتناپذیر است.",
+ "aboutToPrefix": "در حال حذف ",
+ "aboutToSuffix": " هستید.",
+ "deleteAllAria": "حذف همهٔ محیطها",
+ "deleteAll": "حذف همهٔ محیطها",
+ "deleteAllOn": "اپ پروژه و همهٔ محیطهای شاخه زیر آن را حذف میکند.",
+ "deleteAllOff": "فقط این محیط را حذف میکند. شاخههای دیگر میمانند.",
+ "scanning": "در حال اسکن سرویسها و والیومهای متصل…",
+ "wipeAria": "دادههای سرویس هم پاک شود",
+ "wipeLabelOne": "دادههای سرویس هم پاک شود ({count} والیوم)",
+ "wipeLabelOther": "دادههای سرویس هم پاک شود ({count} والیوم)",
+ "wipeOn": "والیومها برای همیشه حذف میشوند — محتوای پایگاهداده، کشها، آپلودها، هر چیزی که این سرویسها ذخیره کردهاند از بین میرود.",
+ "wipeOff": "خاموش: کانتینرها حذف میشوند اما والیومهای نامدار روی دیسک میمانند. بعداً با نام دوباره وصلشان کنید.",
+ "appData": "دادهٔ اپ",
+ "volOne": "{count} والیوم",
+ "volOther": "{count} والیوم",
+ "amberWipe": "همهٔ استقرارها، دامنهها، متغیرهای محیط و دادهٔ پایدار سرویس برای همیشه حذف میشوند.",
+ "amberNoWipe": "همهٔ استقرارها، دامنهها، متغیرهای محیط و تحلیلها برای همیشه حذف میشوند.",
+ "typePrefix": "برای تأیید ",
+ "typeSuffix": " را تایپ کنید.",
+ "cancel": "لغو",
+ "confirmDeleteWipe": "حذف + پاک کردن داده",
+ "confirmDeleteEnvWipe": "حذف محیط + پاک کردن داده",
+ "confirmDelete": "حذف پروژه",
+ "confirmDeleteEnv": "حذف محیط",
+ "recordOnlyLabel": "فقط از Openship حذف شود",
+ "recordOnlyDesc": "اپ و دادهاش روی سرور شما به اجرا ادامه میدهد — فقط رکورد Openship حذف میشود. بعداً میتوانید دوباره واردش کنید.",
+ "recordOnlyReversible": "برگشتپذیر — اپ روی سرور شما میماند.",
+ "recordOnlyNote": "چیزی روی سرور دست نمیخورد — اپ با دادهاش به اجرا ادامه میدهد. فقط رکورد Openship حذف میشود و بعداً میتوان دوباره واردش کرد.",
+ "confirmRecordOnly": "حذف رکورد",
+ "linkedTitleOne": "{count} پروژه به این اپ متصل است",
+ "linkedTitleOther": "{count} پروژه به این اپ متصل هستند",
+ "linkedNote": "با دادهٔ دستنخورده به اجرا ادامه میدهند — فقط این اتصال را از دست میدهند. برای اعمال آن دوباره مستقرشان کنید.",
+ "linkedNoteRecordOnly": "به اجرا ادامه میدهند، اما لینک همراه رکورد میرود — ورود دوبارهٔ این اپ بعداً آن را برنمیگرداند."
+ },
+ "routing": {
+ "title": "مسیریابی",
+ "description": "بازنویسیها، تغییر مسیرها و هدرها. با ذخیره اعمال میشود — بدون بازسازی.",
+ "saving": "در حال ذخیره…",
+ "save": "ذخیره",
+ "summaryNone": "چیزی پیکربندی نشده",
+ "summaryActive": "پیکربندیشده",
+ "toast": {
+ "title": "مسیریابی",
+ "updated": "مسیریابی بهروز شد — روی استقرار زنده اعمال شد.",
+ "failed": "بهروزرسانی مسیریابی ناموفق بود"
+ }
+ },
+ "domains": {
+ "status": {
+ "verified": "تأییدشده",
+ "active": "فعال",
+ "failed": "ناموفق",
+ "removing": "در حال حذف",
+ "pending": "در انتظار"
+ },
+ "ssl": {
+ "includedByHost": "شاملشده توسط میزبان",
+ "active": "فعال",
+ "provisioning": "در حال صدور",
+ "expired": "منقضی",
+ "error": "خطا",
+ "inactive": "غیرفعال",
+ "external": "TLS خارجی",
+ "manual": "گواهی دستی"
+ },
+ "typeCustom": "دامنهٔ سفارشی",
+ "typeFree": "زیردامنهٔ رایگان",
+ "primaryDomainTitle": "دامنهٔ اصلی",
+ "domainNTitle": "دامنهٔ {n}",
+ "portLabel": "پورت {port}",
+ "noPortSelected": "پورتی انتخاب نشده",
+ "meta": {
+ "accessTitle": "URL دسترسی",
+ "accessSubtitle": "نقطهٔ پایانی توسعهٔ محلی",
+ "local": "محلی",
+ "availableOnMachine": "روی این ماشین در دسترس",
+ "primaryTitle": "دامنهٔ اصلی",
+ "primaryAcross": "مسیر اصلی در {count} دامنه",
+ "hostManaged": "URL تولید مدیریتشده توسط میزبان",
+ "customProduction": "دامنهٔ تولید سفارشی"
+ },
+ "route": {
+ "disabled": "غیرفعال",
+ "routePaused": "مسیر متوقف شده",
+ "serviceDisabled": "سرویس غیرفعال است",
+ "internal": "داخلی",
+ "notExposed": "منتشر نشده",
+ "public": "عمومی"
+ },
+ "actions": {
+ "visit": "بازدید",
+ "editDomains": "ویرایش دامنهها",
+ "hideSetup": "پنهان کردن راهاندازی",
+ "addDomain": "افزودن دامنه"
+ },
+ "add": {
+ "title": "افزودن دامنه",
+ "description": "یک مسیر اضافه کنید: دامنهٔ رایگان یا سفارشی و پورتی که به آن نگاشت میشود را انتخاب کنید.",
+ "free": "زیردامنهٔ رایگان",
+ "custom": "دامنهٔ سفارشی",
+ "domainName": "نام دامنه",
+ "subdomain": "زیردامنه",
+ "customPlaceholder": "yourdomain.com",
+ "defaultAppName": "my-app",
+ "mapsToPort": "نگاشت به پورت",
+ "servesPath": "سرو مسیر",
+ "servesPathHint": "درخواستهای این دامنه از این مسیر در خروجی ساخت سرو میشوند.",
+ "servesPathRedeploy": "مسیرهای جدید در استقرار بعدی اثر میگذارند.",
+ "includeWww": "شامل www",
+ "includeWwwDesc": "رکوردهایی هم برای www.{domain} بساز",
+ "includeWwwFallback": "yourdomain.com",
+ "externalIngress": "ورودی خارجی و TLS",
+ "externalIngressDesc": "TLS در بالادست پایان مییابد (Cloudflare Tunnel، متعادلکنندهٔ بار). فقط با TXT تأیید میشود — دامنه لازم نیست به این سرور اشاره کند و گواهی صادر نمیکنیم.",
+ "noWww": "آن دامنه از قبل روی این پروژه است.",
+ "adding": "در حال افزودن...",
+ "submit": "افزودن دامنه"
+ },
+ "dns": {
+ "title": "رکوردهای DNS",
+ "descPreview": "این رکوردها را در ارائهدهندهٔ DNS اضافه کنید، سپس افزودن دامنه را بزنید تا وصل شود",
+ "descApply": "این رکوردها را در ارائهدهندهٔ DNS اعمال کنید، سپس منتظر انتشار بمانید",
+ "infoPreview": "اول این رکوردها را اضافه کنید — بعد از افزودن عوض نمیشوند، پس انتشار از الان شروع میشود. سپس افزودن دامنه را بزنید تا وصل شود و در نهایت پس از حل شدن DNS تأیید کنید.",
+ "infoApply": "تغییرات DNS ممکن است تا ۴۸ ساعت در سراسر جهان منتشر شوند. وقتی رکوردها حل شدند، تأیید را در پایین بزنید — DNS را بررسی میکنیم، دامنه را فعال میکنیم و SSL را در پسزمینه صادر میکنیم.",
+ "verifying": "در حال تأیید...",
+ "verify": "تأیید {hostname}"
+ },
+ "records": {
+ "toggle": "رکوردهای DNS",
+ "hint": "اینها را در ارائهدهندهٔ DNS اضافه کنید، سپس تأیید را بزنید.",
+ "loading": "در حال بارگذاری رکوردها…",
+ "none": "رکوردی برای افزودن به این دامنه نیست.",
+ "failed": "بارگذاری رکوردهای DNS این دامنه ممکن نشد.",
+ "retry": "تلاش دوباره"
+ },
+ "verifyMissing": {
+ "both": "هنوز نه CNAME و نه رکورد TXT حل نشدهاند — رکوردهای زیر را اضافه کنید، منتظر انتشار DNS بمانید، سپس تأیید کنید.",
+ "cname": "رکورد CNAME هنوز حل نشده — آن را در پایین اضافه کنید، منتظر انتشار DNS بمانید، سپس تأیید کنید.",
+ "txt": "رکورد تأیید TXT هنوز حل نشده — آن را در پایین اضافه کنید، منتظر انتشار DNS بمانید، سپس تأیید کنید."
+ },
+ "portHint": {
+ "body": "چیزی روی پورت {port} پاسخ نداد — درخواستها ممکن است تا وقتی اپ آنجا گوش دهد 502 شوند.",
+ "bodyService": "سرویس «{service}»: چیزی روی پورت {port} پاسخ نداد — درخواستها ممکن است تا وقتی آنجا گوش دهد 502 شوند."
+ },
+ "outputHint": {
+ "body": "خروجی ساختی در {path} پیدا نشد — پوشهٔ خروجی را بررسی کنید و دوباره مستقر کنید."
+ },
+ "overview": {
+ "primary": "اصلی",
+ "mappedTo": "نگاشتشده به",
+ "redirect": "تغییر مسیر",
+ "redirectValue": "{status} به {hostname}",
+ "status": "وضعیت",
+ "ssl": "SSL",
+ "visit": "بازدید"
+ },
+ "cold": {
+ "localUrl": "URL محلی",
+ "type": "نوع",
+ "status": "وضعیت"
+ },
+ "edit": {
+ "title": "ویرایش دامنهها",
+ "descServer": "ویرایش کنید هر دامنه به کدام پورت داخلی مسیر شود.",
+ "descStatic": "ویرایش کنید هر دامنه کدام مسیر استاتیک را سرو کند.",
+ "cancel": "لغو",
+ "saving": "در حال ذخیره...",
+ "save": "ذخیرهٔ تغییرات"
+ },
+ "addRoute": {
+ "cancel": "لغو",
+ "add": "افزودن مسیر",
+ "free": "زیردامنهٔ رایگان",
+ "custom": "دامنهٔ سفارشی",
+ "customPlaceholder": "app.example.com",
+ "defaultServiceName": "my-service",
+ "port": "پورت",
+ "portPlaceholder": "8080",
+ "submit": "افزودن",
+ "loading": "در حال بارگذاری مسیرها...",
+ "emptyPrefix": "هنوز دامنهای نیست. ",
+ "emptyAction": "افزودن مسیر",
+ "emptySuffix": " را بزنید تا دامنهای به پورت یک سرویس اشاره کند."
+ },
+ "editRoute": {
+ "title": "ویرایش مسیر",
+ "internalOnly": "فقط داخلی",
+ "close": "بستن",
+ "disabledWarning": "سرویس غیرفعال است — مسیرها تا فعال شدن دوبارهٔ سرویس غیرفعال میمانند.",
+ "save": "ذخیرهٔ تغییرات"
+ },
+ "menu": {
+ "editRoute": "ویرایش مسیر",
+ "settingPrimary": "در حال تنظیم اصلی...",
+ "setPrimary": "تنظیم بهعنوان اصلی",
+ "verifying": "در حال تأیید...",
+ "verify": "تأیید",
+ "renewing": "در حال تمدید...",
+ "renewSsl": "تمدید SSL",
+ "rechecking": "در حال بررسی دوباره...",
+ "recheckSsl": "بررسی دوبارهٔ SSL",
+ "uploadCert": "آپلود گواهی"
+ },
+ "certUpload": {
+ "title": "آپلود گواهی",
+ "desc": "TLS را از گواهی خودتان سرو کنید (گواهی خودآورده یا Cloudflare Origin CA) بهجای Let's Encrypt. وقتی پروکسی بالادستی جلوی این دامنه است و مبدأ هنوز به گواهی واقعی نیاز دارد (Cloudflare Full-strict) از این استفاده کنید. پس از آپلود گواهی، Certbot برای این دامنه غیرفعال میشود — با آپلود گواهی جدید تمدید کنید.",
+ "certLabel": "گواهی (PEM زنجیرهٔ کامل)",
+ "certPlaceholder": "-----BEGIN CERTIFICATE-----",
+ "keyLabel": "کلید خصوصی (PEM)",
+ "keyPlaceholder": "-----BEGIN PRIVATE KEY-----",
+ "submit": "نصب گواهی",
+ "submitting": "در حال نصب...",
+ "close": "لغو"
+ },
+ "toast": {
+ "enterPort": "پورتی را که این دامنه باید به آن مسیر شود وارد کنید (1–65535).",
+ "addDomainTitle": "افزودن دامنه",
+ "addDomainFailed": "افزودن دامنه ناموفق بود",
+ "addDomainFailedTitle": "افزودن دامنه ناموفق بود",
+ "addedCustom": "{label} اضافه شد — رکوردهای DNS را اعمال کنید، سپس تأیید کنید.",
+ "addedFree": "{label} اضافه شد.",
+ "copied": "در کلیپبورد کپی شد",
+ "verifiedSuccess": "{hostname} تأیید شد — SSL در پسزمینه صادر میشود.",
+ "verifiedTitle": "دامنه تأیید شد",
+ "verifyNotYet": "{hostname} هنوز تأیید نشد.",
+ "verifyFailedTitle": "تأیید ناموفق بود",
+ "verifyFailed": "تأیید دامنه ناموفق بود.",
+ "sslRenewed": "SSL برای {hostname} تمدید شد.",
+ "sslRenewFailed": "تمدید SSL برای {hostname} ناموفق بود.",
+ "sslTitle": "SSL",
+ "sslVerified": "SSL برای {hostname} تأیید شد.",
+ "sslNoCert": "هنوز گواهی معتبری برای {hostname} پیدا نشد. اگر تازه مستقر کردهاید، کمی به Let's Encrypt فرصت دهید، سپس دوباره بررسی کنید.",
+ "sslRecheckFailed": "بررسی دوبارهٔ SSL برای {hostname} ناموفق بود.",
+ "certUploaded": "گواهی برای {hostname} نصب شد.",
+ "certUploadFailed": "نصب گواهی برای {hostname} ناموفق بود.",
+ "completeEndpoints": "قبل از ذخیره همهٔ دامنهها و پورتهای نگاشتشده را کامل کنید",
+ "domainsTitle": "دامنهها",
+ "routingUpdated": "مسیریابی دامنه بهروز شد",
+ "routingUpdateFailed": "بهروزرسانی مسیریابی دامنه ناموفق بود",
+ "primaryUpdated": "دامنهٔ اصلی بهروز شد",
+ "setPrimaryFailed": "تنظیم دامنهٔ اصلی ناموفق بود",
+ "routeUpdateFailed": "بهروزرسانی مسیر سرویس ناموفق بود",
+ "publishing": "در حال انتشار — استقرار برای نصب لبه و اعمال دامنه…",
+ "publishFailed": "دامنه ذخیره شد، اما استقرار شروع نشد — برای انتشار دوباره مستقر کنید.",
+ "enterPortShort": "پورتی را که این دامنه باید به آن مسیر شود وارد کنید.",
+ "noServicePort": "هیچ سرویسی پورت {port} را منتشر نمیکند. اول آن پورت را به یک سرویس اضافه کنید.",
+ "enterCustom": "یک دامنهٔ سفارشی وارد کنید.",
+ "enterSubdomain": "یک زیردامنه وارد کنید."
+ },
+ "edge": {
+ "setUp": "راهاندازی لبه",
+ "ready": "لبه آماده است",
+ "checking": "در حال بررسی لبه…",
+ "recheck": "بررسی دوباره",
+ "foreignProxyHint": "پروکسی دیگری 80/443 را در اختیار دارد"
+ },
+ "diagnosis": {
+ "title": "چرا کار نمیکند",
+ "noneYet": "هنوز بررسیای اجرا نشده، پس نتیجهای برای نمایش نیست. رکوردهای DNS نشاندادهشده برای این دامنه را اضافه کنید، سپس تأیید را اجرا کنید.",
+ "attempts": "{count} تلاش ناموفق تاکنون.",
+ "pillHint": "نمایش علت"
+ }
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/fa/projects.json b/apps/dashboard/src/i18n/locales/fa/projects.json
new file mode 100644
index 000000000..4407b0403
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/fa/projects.json
@@ -0,0 +1,516 @@
+{
+ "list": {
+ "loading": "در حال بارگذاری...",
+ "countOne": "{count} پروژه",
+ "countOther": "{count} پروژه",
+ "noTargetProjects": "هنوز پروژهای روی این مقصد مستقر نشده.",
+ "noTargetDesc": "یک اپ روی این مقصد مستقر کنید، یا پروژهٔ تازهای بسازید تا شروع کنید."
+ },
+ "status": {
+ "live": "زنده",
+ "paused": "متوقف",
+ "attention": "اقدام لازم است",
+ "queued": "در صف",
+ "building": "در حال ساخت",
+ "deploying": "در حال استقرار",
+ "migrating": "در حال مهاجرت",
+ "failed": "ناموفق",
+ "cancelled": "لغو شده",
+ "deleting": "در حال حذف",
+ "draft": "پیشنویس"
+ },
+ "serverCta": {
+ "title": "استقرار روی سرور خودتان",
+ "description": "سروری را از طریق SSH وصل کنید تا پروژهها را روی زیرساخت خودتان در کنار Openship Cloud اجرا کنید.",
+ "button": "اتصال سرور"
+ },
+ "filters": {
+ "title": "فیلتر بر اساس مقصد",
+ "subtitle": "پروژهها کجا مستقر شدهاند",
+ "allProjects": "همهٔ پروژهها",
+ "cloud": "ابر Openship",
+ "local": "محلی"
+ },
+ "card": {
+ "liveVersion": "نسخهٔ زنده v{version}",
+ "appBadge": "اپ",
+ "sourceLocal": "محلی",
+ "services": "سرویسها",
+ "static": "استاتیک",
+ "standalone": "مستقل",
+ "worker": "Worker",
+ "updateAvailable": "بهروزرسانی"
+ },
+ "hosting": {
+ "cloud": "Cloud",
+ "server": "سرور",
+ "local": "محلی"
+ },
+ "time": {
+ "justNow": "همین الان",
+ "minutesAgo": "{count} دقیقه پیش",
+ "hoursAgo": "{count} ساعت پیش",
+ "daysAgo": "{count} روز پیش",
+ "monthsAgo": "{count} ماه پیش"
+ },
+ "deploymentCard": {
+ "visit": "بازدید"
+ },
+ "env": {
+ "productionFallback": "تولید",
+ "switchAria": "تغییر محیط",
+ "addBranchAria": "افزودن شاخه",
+ "searchBranches": "جستجوی شاخهها",
+ "noBranches": "شاخهای پیدا نشد",
+ "environmentName": "نام محیط",
+ "branchLabel": "برچسب شاخه",
+ "cancel": "لغو",
+ "create": "ایجاد",
+ "manualEnvironment": "محیط دستی",
+ "toastBranchesTitle": "شاخهها",
+ "toastEnvironmentTitle": "محیط",
+ "failedLoadBranches": "بارگذاری شاخهها ناموفق بود",
+ "failedCreateEnvironment": "ایجاد محیط ناموفق بود"
+ },
+ "detail": {
+ "breadcrumbDashboard": "داشبورد",
+ "projectFallback": "پروژه",
+ "overviewFallback": "نمای کلی"
+ },
+ "help": {
+ "contactSupport": "تماس با پشتیبانی",
+ "reportIssue": "گزارش مشکل",
+ "sendFeedback": "ارسال بازخورد",
+ "documentation": "مستندات",
+ "joinCommunity": "پیوستن به جامعه"
+ },
+ "delete": {
+ "successProject": "پروژه با موفقیت حذف شد",
+ "successEnvironment": "محیط با موفقیت حذف شد",
+ "orphanCleanup": "پروژه حذف شد. {count} منبع راه دور وقتی سرور در دسترس باشد پاکسازی میشود.",
+ "orphanCleanupTitle": "حذف شد (پاکسازی در انتظار)",
+ "partialCleanup": "پروژه حذف شد، اما {count} گام پاکسازی ناموفق بود. عملیات باید بررسی شود.",
+ "partialCleanupTitle": "پاکسازی جزئی",
+ "failed": "حذف پروژه ناموفق بود",
+ "cancellingActiveWork": "لغو کار فعال، سپس حذف…",
+ "cleaningUpTitle": "در حال پاکسازی",
+ "hasActiveWork": "پروژه کار فعال دارد — کمی بعد دوباره تلاش کنید",
+ "cannotDeleteTitle": "نمیتوان حذف کرد",
+ "deletionInProgress": "حذف دیگری برای این پروژه در حال اجراست",
+ "deletionInProgressTitle": "حذف در حال انجام",
+ "confirmForceOrphan": "بعضی منابع از سرور برداشته نشد. پروژه بههرحال حذف شود و بعداً بهطور خودکار پاک شوند؟",
+ "teardownFailedAt": "جمعکردن در این موارد ناموفق بود: {reasons}. دوباره تلاش کنید، یا با حذف اجباری یتیمشان کنید.",
+ "teardownFailed": "جمعکردن ناموفق بود",
+ "cleanupFailedTitle": "پاکسازی ناموفق بود",
+ "alreadyDeleted": "پروژه از قبل حذف شده",
+ "forceModalTitle": "از مبدأ برداشته نشد",
+ "forceModalBody": "به زمان اجرا نرسیدیم (یا مدام شکست خورد)، بنابراین این پروژه جمع نشد. میتوانید بههرحال از Openship حذفش کنید — رکورد الان برداشته میشود و منابع باقیمانده وقتی سرور در دسترس باشد بهطور خودکار پس گرفته میشوند.",
+ "forceModalConfirm": "حذف از ذخیرهسازی",
+ "forceModalCancel": "لغو",
+ "unlinked": "از {projects} جدا شد — برای اعمال دوباره مستقرشان کنید.",
+ "unlinkedTitle": "پروژههای متصل بهروز شد"
+ },
+ "connections": {
+ "useInProject": "استفاده در یک پروژه",
+ "title": "استفاده در یک پروژه",
+ "subtitle": "این پایگاهداده را بهعنوان اتصال آماده به پروژهٔ دیگری وصل کنید.",
+ "targetLabel": "پروژه",
+ "targetPlaceholder": "یک پروژه انتخاب کنید…",
+ "valueLabel": "مقدار برای تزریق",
+ "valuesLabel": "مقادیر برای تزریق",
+ "connectedN": "{count} مقدار به {project} وصل شد — برای اعمال دوباره مستقر کنید.",
+ "modeInternal": "داخلی (پیشنهادی)",
+ "modeInternalDesc": "بهصورت خصوصی روی شبکهٔ مشترک دسترسی — بدون پورت عمومی. همان سرور خودمیزبان.",
+ "modePublic": "عمومی",
+ "modePublicDesc": "از host:port منتشرشده استفاده کنید. همهجا کار میکند؛ پورت پایگاهداده باید باز باشد.",
+ "envKeyLabel": "متغیر محیطی",
+ "redeployHint": "در استقرار بعدی مقصد اعمال میشود.",
+ "cancel": "لغو",
+ "connect": "اتصال",
+ "connected": "متصل شد — برای اعمال، مقصد را دوباره مستقر کنید.",
+ "failed": "اتصال سرویس ممکن نشد.",
+ "cardTitle": "سرویسهای متصل",
+ "removed": "اتصال برداشته شد — برای اعمال دوباره مستقر کنید.",
+ "remove": "برداشتن",
+ "modeInternalShort": "داخلی",
+ "modePublicShort": "عمومی",
+ "guideIntroFallback": "پروژهٔ شما این اتصال را آمادهٔ استفاده میگیرد.",
+ "whatYouGet": "اپ شما چه میگیرد",
+ "advancedValues": "پیشرفته",
+ "copy": "کپی",
+ "copied": "کپی شد",
+ "reveal": "نمایش",
+ "hide": "پنهان",
+ "openLocalhost": "باز کردن روی localhost",
+ "forwardLocalhost": "فوروارد به localhost",
+ "openShort": "باز کردن",
+ "openUrl": "باز کردن در زبانهٔ جدید",
+ "forwardShort": "فوروارد",
+ "usedByTitle": "استفادهشده توسط",
+ "usedByHint": "{count} پروژه به اتصال این اپ وابستهاند. از پروژهٔ مصرفکننده جدا کنید.",
+ "usedByOpen": "باز کردن پروژه",
+ "linkedCountOne": "{count} اپ پیوندشده",
+ "linkedCountOther": "{count} اپ پیوندشده",
+ "linkedHint": "در برابر این پروژه اجرا میشود، نه توسط آن.",
+ "linkedBadge": "پیوندشده",
+ "unlink": "جدا کردن",
+ "openApp": "باز کردن اپ",
+ "internalTitle": "آدرس داخلی",
+ "internalDescription": "از سرویس دیگری روی همان سرور بهصورت خصوصی به این پروژه برسید — بدون پورت عمومی."
+ },
+ "sidebar": {
+ "project": "پروژه",
+ "untitledProject": "پروژهٔ بدون عنوان",
+ "openAria": "باز کردن {name}",
+ "openProjectFallback": "پروژه",
+ "open": "باز کردن",
+ "local": "محلی",
+ "production": "تولید",
+ "lastDeploy": "آخرین استقرار",
+ "noDomain": "بدون دامنه",
+ "addDomain": "افزودن دامنه",
+ "tabs": {
+ "appSettings": "تنظیمات",
+ "overview": "نمای کلی",
+ "services": "اپها و سرویسها",
+ "domains": "دامنهها و مسیرها",
+ "deployments": "استقرارها",
+ "health": "سلامت",
+ "monitoring": "پایش",
+ "source": "منبع",
+ "webhooks": "وبهوکها",
+ "runtime": "پیکربندی",
+ "logs": "لاگها",
+ "backup": "پشتیبان",
+ "advanced": "پیشرفته"
+ }
+ },
+ "notFound": {
+ "title": "پروژه پیدا نشد",
+ "subtitle": "این پروژه را پیدا نکردیم",
+ "bodyPrefix": "پروژهٔ",
+ "bodySuffix": "در فضای کاری شما وجود ندارد.",
+ "reasonDeleted": "ممکن است پروژه حذف شده باشد",
+ "reasonAccess": "ممکن است به این پروژه دسترسی نداشته باشید",
+ "reasonUrl": "ممکن است URL دامنه نادرست باشد",
+ "dashboard": "داشبورد",
+ "reload": "بارگذاری دوباره",
+ "needHelp": "کمک میخواهید؟",
+ "documentation": "مستندات",
+ "support": "پشتیبانی"
+ },
+ "draft": {
+ "headingFailed": "آخرین استقرار تمام نشد",
+ "headingCancelled": "استقرار لغو شد",
+ "headingReady": "آمادهٔ استقرار",
+ "subtextDraft": "این پروژه هنوز مستقر نشده. مستقرش کنید تا URL زنده، لاگ و تحلیل بگیرید.",
+ "subtextOther": "این پروژه هنوز استقرار زندهای ندارد. منبع را بررسی کنید و دوباره تلاش کنید.",
+ "deployNow": "الان استقرار کن",
+ "connectSource": "اتصال منبع",
+ "settings": "تنظیمات استقرار",
+ "detailsTitle": "جزئیات",
+ "detailsDescription": "این پروژه چه چیزی و کجا مستقر میکند",
+ "type": "نوع",
+ "typeApp": "اپ",
+ "typeProject": "پروژه",
+ "target": "مقصد",
+ "targetPending": "هنگام استقرار انتخاب میشود",
+ "services": "سرویسها",
+ "created": "ایجاد شده",
+ "attemptsTitle": "تلاشهای استقرار",
+ "attemptsDescription": "هر ساخت این پروژه — برای باز کردن یکی را بزنید",
+ "sourceTitle": "منبع",
+ "sourceDescription": "این پروژه از کجا مستقر میشود",
+ "repository": "مخزن",
+ "branch": "شاخه",
+ "localPath": "مسیر محلی",
+ "framework": "فریمورک",
+ "build": "ساخت",
+ "noSourceText": "هنوز منبعی وصل نیست.",
+ "connectLink": "اتصال مخزن یا مسیر محلی",
+ "managedImages": "ایمیجهای کانتینر مدیریتشده",
+ "managedImagesText": "این اپ از ایمیجهای ازپیشساخته اجرا میشود — مخزنی برای اتصال نیست. فقط استقرار کنید.",
+ "deleteTitle": "حذف پروژه",
+ "deleteDescription": "حذف این پروژه — برگشتناپذیر است",
+ "deleteConfirmPrefix": "حذف دائمی",
+ "deleteConfirmSuffix": "؟",
+ "cancel": "لغو",
+ "delete": "حذف"
+ },
+ "overview": {
+ "platformCloud": "ابر Openship",
+ "platformServer": "خودمیزبان (سرور)",
+ "platformLocal": "خودمیزبان (محلی)",
+ "modeStatic": "سایت استاتیک",
+ "modeStandalone": "مستقل",
+ "modeServer": "سرور",
+ "modeWorker": "Worker",
+ "infrastructure": "زیرساخت",
+ "platform": "پلتفرم",
+ "mode": "حالت",
+ "port": "پورت",
+ "sourceCicd": "منبع و CI/CD",
+ "repository": "مخزن",
+ "notConnected": "وصل نیست",
+ "branch": "شاخه",
+ "autoDeploy": "استقرار خودکار",
+ "webhook": "وبهوک",
+ "traffic": "ترافیک",
+ "noTrafficData": "هنوز دادهٔ ترافیک نیست",
+ "services": "سرویسها",
+ "noServicesConnected": "سرویسی وصل نیست",
+ "manage": "مدیریت",
+ "topPaths": "مسیرهای برتر",
+ "requestCount": "{count} درخواست",
+ "statusActive": "فعال",
+ "statusOff": "خاموش",
+ "server": "سرور"
+ },
+ "stats": {
+ "serverRequests": "درخواستهای سرور",
+ "uniqueIPs": "IPهای یکتا",
+ "avgResponse": "میانگین پاسخ",
+ "bandwidthOut": "پهنای باند خروجی",
+ "bandwidth": "پهنای باند",
+ "requestsSubtext": "{total} در مجموع، میانگین {avg}/ساعت",
+ "uniqueIPsSubtext": "{pct}٪ از کل",
+ "responseTime": "زمان پاسخ",
+ "bandwidthInSubtext": "{value} ورودی",
+ "loading": "در حال بارگذاری...",
+ "noTraffic": "هنوز ترافیکی ثبت نشده",
+ "noVisitors": "هنوز بازدیدکنندهای نیست",
+ "waitingRequests": "در انتظار درخواست",
+ "noTransfer": "هنوز انتقالی نیست"
+ },
+ "monitoring": {
+ "noDataTitle": "هنوز دادهٔ پایش نیست",
+ "noDataDescription": "پس از اینکه اپ مستقرشده ترافیک بگیرد، تحلیلها اینجا ظاهر میشوند.",
+ "resourcesTitle": "مصرف منابع",
+ "resourcesSubtitle": "زنده، هر چند ثانیه تازهسازی میشود",
+ "cpu": "CPU",
+ "memory": "حافظه",
+ "network": "شبکه",
+ "diskIo": "I/O دیسک",
+ "ofCores": "از {cores} هسته",
+ "ofMemory": "از {total}",
+ "servicesTitle": "سرویسها",
+ "scrollServicesLeft": "اسکرول سرویسها به چپ",
+ "scrollServicesRight": "اسکرول سرویسها به راست",
+ "unsupportedTitle": "مصرف منابع در دسترس نیست",
+ "reconnect": "اتصال دوباره",
+ "live": "زنده",
+ "offline": "قطع شده",
+ "visitorsTitle": "بازدیدکنندگان بر اساس کشور",
+ "visitorsSubtitle": "{total} درخواست از {countries} کشور",
+ "visitors": "بازدیدکنندگان",
+ "approximate": "تقریبی",
+ "geoUnavailableTitle": "دادهٔ کشور روی این لبه در دسترس نیست",
+ "geoUnavailableBody": "پروکسی لبه نمیتواند کشور بازدیدکنندگان را تشخیص دهد، بنابراین نقشه خالی میماند. مجموع ترافیک تحت تأثیر نیست.",
+ "noCountries": "هنوز دادهٔ کشوری برای این بازه نیست.",
+ "statusesTitle": "پاسخها",
+ "share": "{pct}٪",
+ "visitorDays": "روز-بازدیدکننده",
+ "peakDayTooltip": "شلوغترین روز: {peak}. بازدیدکنندهٔ برگشتی روزی یکبار شمرده میشود، پس مجموع یک کران بالاست.",
+ "historyTitle": "مصرف در طول زمان",
+ "historyEmptyTitle": "هنوز تاریخچهٔ مصرف نیست",
+ "historyEmpty": "CPU و حافظه هر چند دقیقه نمونهبرداری میشوند — اولین نقاط کمی پس از شروع اجرای اپ اینجا ظاهر میشوند.",
+ "historyWaiting": "در انتظار اولین نمونه",
+ "allServices": "همهٔ سرویسها",
+ "requests": "درخواست",
+ "moreCountries": "+{count} کشور دیگر",
+ "previewSample": "پیشنمایش با دادهٔ نمونه",
+ "previewActive": "دادهٔ نمونه — واقعی نیست",
+ "previewExit": "خروج از پیشنمایش",
+ "colorTheme": "پوسته",
+ "colorFlag": "پرچمها",
+ "tooltipRequests": "{count} درخواست · {pct}٪",
+ "allDomains": "همهٔ دامنهها",
+ "domainScope": "محدود به یک دامنه",
+ "liveHits": "زنده",
+ "liveHitsHint": "هر درخواست ورودی را روی نقشه از همان استریمی که زبانهٔ لاگها نشان میدهد پویانمایی کن",
+ "liveHitsWaiting": "در انتظار درخواست…",
+ "statusesUnclassified": "{count} بدون کلاس وضعیت",
+ "trafficTitle": "نمای کلی ترافیک",
+ "trafficSummary": "{count} درخواست",
+ "servicesMore": "+{count} مورد دیگر",
+ "servicesLess": "نمایش کمتر",
+ "pathsOff": {
+ "title": "مسیرهای برتر خاموش است",
+ "desc": "شمارش هر مسیر تنها معیاری است که رایگان نیست — حدود {share} از کار لبه روی هر درخواست. بقیهٔ این زبانه بدون هزینه جمع میشود.",
+ "cta": "فعال کردن مسیرهای برتر",
+ "enabling": "در حال فعالسازی…",
+ "disable": "خاموش کردن مسیرهای برتر"
+ },
+ "liveFeed": "ضربههای اخیر",
+ "liveFeedHint": "فهرست ضربههای اخیر را روی نقشه نشان بده (زبانهٔ لاگها جزئیات کامل را دارد)"
+ },
+ "services": {
+ "failedLoad": "بارگذاری سرویسها ناموفق بود",
+ "retry": "تلاش دوباره",
+ "refresh": "تازهسازی",
+ "addService": "افزودن",
+ "allServices": "همه",
+ "countOne": "{count} اپ / سرویس",
+ "countOther": "{count} اپ و سرویس",
+ "chooseService": "یکی را انتخاب کنید تا صفحهٔ کامل تنظیماتش باز شود.",
+ "emptyTitle": "هنوز اپ یا سرویسی نیست",
+ "emptyDescription": "پایگاهداده، کش، یا هر ایمیج کانتینر را در کنار پروژه اجرا کنید. از کاتالوگ انتخاب کنید یا هر ایمیج Docker را بچسبانید.",
+ "driftCountOne": "{count} سرویس در مخزن تغییر کرده",
+ "driftCountOther": "{count} سرویس در مخزن تغییر کردهاند",
+ "driftDescription": "docker-compose مخزن مقادیری را که اینجا ویرایش کرده بودید عوض کرده. ویرایشهای شما هنوز زندهاند — برای هر کدام مال خودتان را نگه دارید یا مال مخزن را بگیرید.",
+ "keepMine": "مال من بماند",
+ "acceptUpstream": "پذیرش بالادست",
+ "public": "عمومی",
+ "internal": "داخلی",
+ "upstreamChange": "تغییر بالادست",
+ "port": "پورت {port}",
+ "toastServiceTitle": "سرویس",
+ "toastAddedDeploying": "{name} اضافه شد — در حال استقرار…",
+ "toastStarting": "{name} در حال شروع است",
+ "toastSavedDeploy": "{name} ذخیره شد — پروژه را مستقر کنید تا شروع شود",
+ "toastDeployFailed": "استقرار ناموفق بود",
+ "toastAppliedRepo": "{name}: تغییرات مخزن اعمال شد",
+ "toastKeptEdits": "{name}: ویرایشهای شما نگه داشته شد",
+ "failedResolveDrift": "حل اختلاف ناموفق بود",
+ "failedCreateService": "ایجاد سرویس ناموفق بود",
+ "requestFailed": "درخواست ناموفق بود"
+ },
+ "serviceStatus": {
+ "running": "در حال اجرا",
+ "stopped": "متوقف",
+ "disabled": "غیرفعال",
+ "failed": "ناموفق",
+ "starting": "در حال شروع",
+ "restarting": "در حال ریاستارت",
+ "unknown": "نامشخص"
+ },
+ "routingRetry": {
+ "title": "مسیریابی دامنه نیاز به توجه دارد",
+ "description": "اپ روی سرور زنده است، اما مسیرهای جلوی آن همگام نشدند. تعمیر را دوباره امتحان کنید، یا DNS و لبهٔ این سرور را بررسی کنید.",
+ "retry": "تلاش دوبارهٔ مسیریابی",
+ "retrying": "در حال تلاش دوباره…",
+ "success": "مسیریابی دامنه همگام شد.",
+ "failed": "همگامسازی مسیریابی دامنه ممکن نشد."
+ },
+ "deployBlocked": {
+ "redeploy": "استقرار دوباره"
+ },
+ "redeploy": {
+ "deployStartedLong": "استقرار شروع شد — بیشتر از معمول طول میکشد، استقرارها باز میشود.",
+ "deployingTitle": "در حال استقرار",
+ "couldNotRefresh": "تازهسازی استقرار ممکن نشد. برای جزئیات لاگ استقرار را ببینید.",
+ "couldNotRedeploy": "شروع استقرار دوباره ممکن نشد. برای جزئیات لاگ استقرار را ببینید.",
+ "errorTitle": "خطا",
+ "failedRedeploy": "شروع استقرار دوباره ناموفق بود",
+ "actionRequiredTitle": "اقدام لازم است — بعضی سرویسها ناموفق بودند",
+ "actionRequiredDescription": "آخرین انتشار این پروژه با سرویسهای ناموفق مستقر شد و منتظر نگهداشتن یا رد شماست.",
+ "reviewDeployment": "بازبینی استقرار",
+ "newCommitTitle": "کامیت جدید موجود است",
+ "newCommitOn": "روی",
+ "newCommitDeployedOn": "— شما روی این مستقر هستید",
+ "newVersionTitle": "نسخهٔ جدید موجود است",
+ "newVersionAvailable": "انتشار تازهتری موجود است:",
+ "newVersionDeployed": "— شما روی این مستقر هستید",
+ "deployVersion": "استقرار نسخهٔ جدید",
+ "deploying": "در حال استقرار…",
+ "redeployLatest": "استقرار دوبارهٔ آخرین",
+ "deployLatestTitle": "استقرار آخرین تغییرات",
+ "deployLatestMulti": "آخرین کامیت را میگیرد و فقط سرویسهایی را که فایلهایشان از آخرین استقرار عوض شده دوباره میسازد.",
+ "deployLatestSingle": "آخرین کامیت را میگیرد و با پیکربندی فعلی دوباره مستقر میکند.",
+ "deployingButton": "در حال استقرار...",
+ "redeployProject": "استقرار دوبارهٔ پروژه",
+ "refreshEnv": "تازهسازی محیط",
+ "rebuildAll": "ساخت دوبارهٔ همه",
+ "noPublicDomainTitle": "دامنهٔ عمومی وصل نیست",
+ "noPublicDomainDescOne": "این پروژه {count} سرویس با پورت منتشرشده دارد، اما هیچکدام دامنهٔ در دسترس ندارند. اگر الان استقرار کنید، استک داخلی اجرا میشود اما کاربران از URL عمومی به آن دسترسی نخواهند داشت.",
+ "noPublicDomainDescOther": "این پروژه {count} سرویس با پورت منتشرشده دارد، اما هیچکدام دامنهٔ در دسترس ندارند. اگر الان استقرار کنید، استک داخلی اجرا میشود اما کاربران از URL عمومی به آن دسترسی نخواهند داشت.",
+ "openServices": "باز کردن سرویسها",
+ "deployAnyway": "بههرحال استقرار کن",
+ "suggestedFix": "اصلاح پیشنهادی",
+ "fixStep1": "زبانهٔ سرویسها را باز کنید.",
+ "fixStep2": "سرویسی را که باید عمومی باشد انتخاب کنید.",
+ "fixStep3": "انتشار دامنه را فعال کنید و پورت عمومی را انتخاب کنید."
+ },
+ "incomingWebhooks": {
+ "title": "وبهوکهای ورودی",
+ "subtitle": "استقرار یا کار را از یک URL خارجی تریگر کنید. هر وبهوک آدرس و احراز هویت خودش را دارد.",
+ "create": "وبهوک جدید",
+ "loading": "در حال بارگذاری وبهوکها…",
+ "empty": "هنوز وبهوک ورودی نیست. یکی بسازید تا استقرار یا کار را از URL خارجی تریگر کنید.",
+ "deleteConfirm": "وبهوک «{name}» حذف شود؟ URL آن فوراً از کار میافتد.",
+ "removed": "وبهوک حذف شد.",
+ "rotated": "سکرت چرخانده شد — مقدار تازه را به فراخواننده بدهید.",
+ "created": "وبهوک ایجاد شد.",
+ "failed": "مشکلی پیش آمد.",
+ "toggleAria": "فعال یا غیرفعال کردن این وبهوک",
+ "rotate": "چرخاندن سکرت",
+ "delete": "حذف",
+ "urlLabel": "URL تحویل",
+ "secretLabel": "سکرت",
+ "copy": "کپی",
+ "copied": "کپی شد",
+ "reveal": "نمایش",
+ "hide": "پنهان",
+ "authBadge": {
+ "token": "Token",
+ "hmac": "HMAC",
+ "none": "باز"
+ },
+ "summaryDeploy": "این پروژه را دوباره مستقر میکند",
+ "summaryDeployService": "سرویس {service} را دوباره مستقر میکند",
+ "summaryJob": "کار {job} را اجرا میکند",
+ "createTitle": "وبهوک ورودی جدید",
+ "createSubtitle": "انتخاب کنید چه میکند و فراخوانندهها چطور احراز هویت میشوند.",
+ "nameLabel": "نام",
+ "namePlaceholder": "مثلاً هوک استقرار CI",
+ "actionLabel": "اقدام",
+ "actionDeploy": "استقرار",
+ "actionDeployDesc": "وقتی فراخوانده شود این پروژه (یا یک سرویس) را دوباره مستقر میکند.",
+ "actionJob": "اجرای یک کار",
+ "actionJobDesc": "وقتی فراخوانده شود یکی از کارها را اجرا میکند.",
+ "serviceLabel": "سرویس (اختیاری)",
+ "serviceAll": "کل پروژه",
+ "jobLabel": "کار",
+ "jobPlaceholder": "یک کار انتخاب کنید…",
+ "authLabel": "احراز هویت",
+ "authToken": "Token",
+ "authTokenDesc": "یک توکن bearer محرمانه — بهصورت Authorization: Bearer یا ?token= بفرستید.",
+ "authHmac": "امضای HMAC",
+ "authHmacDesc": "فراخوانندهها بدنه را با سکرت مشترک امضا میکنند (X-Signature-256).",
+ "authNone": "باز",
+ "authNoneDesc": "بدون احراز هویت — هرکس URL را داشته باشد میتواند تریگر کند. با احتیاط استفاده کنید.",
+ "cancel": "لغو"
+ },
+ "health": {
+ "title": "سلامت",
+ "subtitle": "خرابیهای کانتینر این پروژه که پایش سلامت ثبت کرده.",
+ "refresh": "تازهسازی",
+ "openTitle": "حوادث باز",
+ "recentTitle": "اخیر ({days} روز)",
+ "emptyOpen": "حادثهٔ بازی نیست.",
+ "emptyRecent": "حادثهای ثبت نشده.",
+ "emptyUnwatched": "چیزی ثبت نمیشود — پایش سلامت خاموش است.",
+ "watchOffTitle": "پایش سلامت کانتینر خاموش است",
+ "watchOffBody": "حادثهای ثبت نمیشود و اعلانی فرستاده نمیشود، پس فهرست خالی اینجا به معنای سلامت همه چیز نیست.",
+ "watchOffCta": "در کارها روشن کنید",
+ "serverDownTitle": "Openship به سرور این پروژه نمیرسد",
+ "serverDownBody": "روی این ماشین برای {duration} چیزی مشاهده نشده، بنابراین حوادث پایین ممکن است قدیمی باشند.",
+ "loadFailed": "بارگذاری حوادث ممکن نشد.",
+ "ongoing": "در جریان برای {duration}",
+ "lasted": "به مدت {duration}",
+ "started": "شروع در {when}",
+ "recovered": "بازیابی شد",
+ "exitCode": "خروج {code}",
+ "restarts": "{count} ریاستارت",
+ "oomKilled": "کمبود حافظه",
+ "showLog": "نمایش گزیدهٔ لاگ",
+ "hideLog": "پنهان کردن گزیدهٔ لاگ",
+ "kinds": {
+ "down": "از کار افتاده",
+ "crash_loop": "حلقهٔ کرش",
+ "unhealthy": "ناسالم",
+ "server_unreachable": "سرور در دسترس نیست"
+ }
+ },
+ "migrationAwaitingHint": "مهاجرت منتظر شماست — انتقال را تمام کنید یا در زبانهٔ پیشرفتهٔ پروژه بازگردانی کنید"
+}
diff --git a/apps/dashboard/src/i18n/locales/fa/servers.json b/apps/dashboard/src/i18n/locales/fa/servers.json
new file mode 100644
index 000000000..ece0e2417
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/fa/servers.json
@@ -0,0 +1,529 @@
+{
+ "github": {
+ "title": "دسترسی GitHub",
+ "desc": "این سرور چگونه برای کلون مخازن خصوصی به GitHub احراز هویت میکند.",
+ "loadFailed": "نتوانستیم وضعیت GitHub را بارگذاری کنیم",
+ "connected": "متصل",
+ "modeToken": "توکن",
+ "modeSshKey": "کلید SSH",
+ "modeDeployKey": "کلیدهای استقرار",
+ "connectDevice": "اتصال با GitHub",
+ "deviceHint": "پیوند را باز کنید و این کد را وارد کنید:",
+ "open": "باز کردن GitHub",
+ "connecting": "در انتظار مجوز…",
+ "connectFailed": "اتصال ناموفق بود",
+ "connectedToast": "GitHub برای این سرور متصل شد",
+ "patPlaceholder": "ghp_… (یک توکن بچسبانید)",
+ "patHint": "توکن دسترسی شخصی که برای این سرور رمزنگاریشده ذخیره میشود.",
+ "patLabel": "توکن دسترسی شخصی",
+ "or": "یا",
+ "save": "ذخیره",
+ "savedToast": "ذخیره شد",
+ "saveFailed": "ذخیره ناموفق بود",
+ "tokenAs": "احراز هویتشده بهعنوان",
+ "sshGenerate": "تولید کلید SSH",
+ "sshAddHint": "این کلید را به حساب GitHub اضافه کنید، سپس استقرار دهید:",
+ "openGithubKeys": "باز کردن کلیدهای SSH گیتهاب",
+ "copy": "کپی",
+ "deployKeyUse": "استفاده از کلید استقرار بهازای مخزن",
+ "deployKeyHint": "Openship اولین باری که آنجا استقرار میدهد یک کلید استقرار فقطخواندنی روی هر مخزن ثبت میکند.",
+ "deployKeyActive": "حالت کلید استقرار فعال است — کلیدها در اولین استقرار ساخته میشوند.",
+ "deployKeysRegistered": "کلید استقرار ثبتشده",
+ "disconnect": "قطع اتصال",
+ "disconnectedToast": "GitHub قطع شد"
+ },
+ "list": {
+ "title": "سرورها",
+ "subtitle": "سرورهای استقرار و زیرساخت را مدیریت کنید",
+ "addServer": "افزودن سرور",
+ "addRemoteServer": "افزودن سرور راه دور",
+ "addThisMachine": "افزودن این ماشین",
+ "addThisMachineError": "نتوانستیم این ماشین را بهعنوان سرور اضافه کنیم.",
+ "emptyTitle": "هنوز سروری نیست",
+ "emptyDescription": "از طریق SSH یک سرور وصل کنید و Openship بقیه را انجام میدهد — Docker، لبه، SSL و استقرارها همه خودکار راهاندازی میشوند.",
+ "addFirstServer": "اولین سرور را اضافه کنید",
+ "whatGetsConfigured": "چه چیزی پیکربندی میشود",
+ "featContainer": "زمان اجرای کانتینر",
+ "featContainerDesc": "هر اپ را در انزوا اجرا میکند",
+ "featProxy": "پروکسی معکوس",
+ "featProxyDesc": "مسیریابی و SSL",
+ "monitoring": "پایش",
+ "monitoringDesc": "بررسیهای سلامت",
+ "gitDesc": "استقرار از منبع",
+ "connected": "متصل",
+ "error": "خطا",
+ "checking": "در حال بررسی",
+ "online": "آنلاین",
+ "offline": "آفلاین",
+ "authKey": "کلید",
+ "authPassword": "رمز عبور",
+ "forwarding": "{n} ارسال",
+ "quickInfo": "اطلاعات سریع",
+ "serverOverview": "نمای کلی سرور",
+ "totalServers": "کل سرورها",
+ "connectedLabel": "متصل",
+ "errors": "خطاها",
+ "seeDocs": "مشاهدهٔ مستندات",
+ "projects": "پروژهها",
+ "regions": "مناطق",
+ "thisServer": "این سرور",
+ "currentHost": "میزبان فعلی",
+ "projectCountOne": "{n} پروژه",
+ "projectCountMany": "{n} پروژه",
+ "infra": {
+ "title": "اجزای پلتفرم",
+ "subtitle": "لبه و ایمیل در سراسر ناوگان شما",
+ "attentionOne": "۱ مورد نیاز به توجه",
+ "attentionMany": "{n} مورد نیاز به توجه",
+ "updates": "{n} با بهروزرسانی",
+ "healthy": "{n} سالم",
+ "allHealthy": "همهٔ اجزا بهروز هستند",
+ "updateAll": "بهروزرسانی همه ({n})",
+ "restartStopped": "راهاندازی مجدد متوقفشدهها ({n})",
+ "scan": "بررسی دوبارهٔ هر سرور",
+ "openServerHint": "برای تمام کردن اینها سرور را باز کنید — نیاز به راهاندازی یا رضایت شما دارند.",
+ "filterAll": "همه",
+ "filterAttention": "نیاز به توجه",
+ "filterUpdates": "بهروزرسانیها",
+ "filterHealthy": "سالم",
+ "searchPlaceholder": "جستجوی سرورها…",
+ "noMatches": "هیچ سروری با این فیلتر جور نیست.",
+ "chipEdgeDown": "لبه پایین",
+ "chipMailDown": "ایمیل پایین",
+ "chipEdgeMissing": "بدون لبه",
+ "chipUpdateOne": "۱ بهروزرسانی",
+ "chipUpdates": "{n} بهروزرسانی",
+ "chipUpdating": "در حال بهروزرسانی",
+ "applyingOne": "در حال بهروزرسانی ۱ جزء",
+ "applyingMany": "در حال بهروزرسانی {n} جزء",
+ "restartingOne": "در حال راهاندازی مجدد ۱ جزء",
+ "restartingMany": "در حال راهاندازی مجدد {n} جزء",
+ "stateQueued": "در صف",
+ "stepPull": "در حال کشیدن ایمیج",
+ "stepRecreate": "در حال بازسازی",
+ "stepVerify": "در حال تأیید",
+ "moreTargets": "+{n} مورد دیگر",
+ "viewLogs": "مشاهدهٔ گزارشها",
+ "doneOne": "۱ جزء بهروز شد",
+ "doneMany": "{n} جزء بهروز شد",
+ "failedOne": "۱ جزء تمام نشد",
+ "failedMany": "{n} جزء تمام نشدند",
+ "skippedOne": "۱ مورد در صفحهٔ خودش نیاز به توجه دارد",
+ "skippedMany": "{n} مورد در صفحهٔ خودشان نیاز به توجه دارند",
+ "nothingToDo": "چیزی برای اعمال نیست",
+ "applyFailed": "نتوانستیم بهروزرسانیها را شروع کنیم"
+ }
+ },
+ "containers": {
+ "subtitle": "کانتینرهای لبه و ایمیل روی این سرور",
+ "updatesAvailableOne": "۱ بهروزرسانی موجود",
+ "updatesAvailableMany": "{n} بهروزرسانی موجود",
+ "scan": "اسکن",
+ "scanning": "در حال اسکن…",
+ "componentEdge": "لبه",
+ "componentMail": "موتور ایمیل",
+ "down": "پایین",
+ "goneEdge": "کانتینر رفته — برای بازسازی راهاندازی را دوباره اجرا کنید.",
+ "goneMail": "کانتینر رفته — برای بازسازی راهاندازی ایمیل را دوباره اجرا کنید.",
+ "legacyEngine": "نصب قدیمی · کانتینری نیست",
+ "update": "بهروزرسانی",
+ "updating": "در حال بهروزرسانی…",
+ "fix": "رفع",
+ "starting": "در حال شروع…",
+ "stopped": "متوقف",
+ "running": "در حال اجرا",
+ "upToDate": "بهروز",
+ "notInstalled": "نصب نشده",
+ "installEdge": "نصب لبه",
+ "edgeAbsentImpactOne": "{n} پروژه اینجا استقرار یافته بدون چیزی که ترافیک را به آن مسیریابی کند.",
+ "edgeAbsentImpactMany": "{n} پروژه اینجا استقرار یافتهاند بدون چیزی که ترافیک را به آنها مسیریابی کند."
+ },
+ "modules": {
+ "title": "بهروزرسانی ماژول موجود است",
+ "checkFailedTitle": "بررسی بهروزرسانی ماژول ناموفق بود",
+ "needsConfirmation": "نیاز به تأیید",
+ "update": "بهروزرسانی",
+ "updating": "در حال بهروزرسانی…",
+ "confirmTitle": "{module} بهروز شود؟",
+ "confirmIntro": "این بهروزرسانی شامل تغییراتی است که OK شما را میخواهند:",
+ "cancel": "لغو",
+ "updateFailed": "بهروزرسانی {module} ناموفق بود"
+ },
+ "tabsNav": {
+ "servers": "سرورها",
+ "cluster": "خوشه",
+ "networking": "شبکه"
+ },
+ "comingSoon": {
+ "badge": "بهزودی",
+ "clusterTitle": "خوشهها",
+ "clusterBody": "سرورها را در یک خوشه گروهبندی کنید و بگذارید Openship استقرارها را بین آنها زمانبندی و متعادل کند.",
+ "networkingTitle": "شبکه",
+ "networkingBody": "پیوندهای خصوصی، مسیریابی مش، و قوانین فایروال در سراسر سرورها — در یک جا مدیریت میشوند."
+ },
+ "form": {
+ "sshConnection": "اتصال SSH",
+ "sshConnectionDesc": "از طریق SSH به سرور وصل شوید",
+ "serverName": "نام سرور",
+ "serverNamePlaceholder": "مثلاً Production، Staging، Dev...",
+ "serverNameHelp": "برچسب اختیاری که در فهرست سرور و هدر نشان داده میشود.",
+ "serverIp": "آدرس IP سرور",
+ "port": "پورت",
+ "portPlaceholder": "مثلاً ۲۲",
+ "username": "نام کاربری",
+ "usernamePlaceholder": "مثلاً root",
+ "authentication": "احراز هویت",
+ "password": "رمز عبور",
+ "sshKey": "کلید SSH",
+ "agent": "Agent",
+ "passwordPlaceholderKeep": "برای نگه داشتن رمز فعلی خالی بگذارید",
+ "passwordPlaceholderEnter": "رمز سرور را وارد کنید",
+ "agentInfoBefore": "با SSH agent این ماشین وصل میشود — مثل VSCode، بدون رمز یا کلید. میزبانی که Openship را اجرا میکند باید از قبل به این سرور دسترسی داشته باشد (کلیدی بارگذاریشده در ",
+ "agentInfoAfter": " آن).",
+ "keyModePaste": "چسباندن / آپلود",
+ "keyModePath": "مسیر میزبان",
+ "keyPath": "مسیر کلید",
+ "keyPathBrowse": "مرور…",
+ "keyPathHelp": "مسیر مطلق کلید خصوصی روی ماشینی که Openship را اجرا میکند.",
+ "keyPaste": "کلید خصوصی",
+ "keyPastePlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----\n…",
+ "keyPasteHelp": "کلیدهای چسباندهشده در حالت سکون رمزنگاری میشوند و هرگز سرور را ترک نمیکنند.",
+ "keyStoredKeep": "کلیدی ذخیره شده — برای جایگزینی یکی جدید بچسبانید، یا برای نگه داشتن خالی بگذارید.",
+ "keyUploadFile": "آپلود فایل کلید",
+ "keyPasteInvalid": "آن فایل شبیه کلید خصوصی معتبر نیست.",
+ "passphrase": "عبارت عبور",
+ "optional": "(اختیاری)",
+ "passphrasePlaceholder": "عبارت عبور را وارد کنید یا خالی بگذارید",
+ "advanced": "پیشرفته",
+ "jumpHost": "میزبان پرش",
+ "extraArgs": "آرگومانهای اضافهٔ SSH",
+ "testing": "در حال آزمایش…",
+ "connected": "متصل",
+ "testConnection": "آزمایش اتصال",
+ "saveChanges": "ذخیرهٔ تغییرات",
+ "saveAndContinue": "ذخیره و ادامه به راهاندازی",
+ "modalTitle": "سرور را اضافه کنید",
+ "modalSubtitle": "از طریق SSH وصل شوید. راهاندازی را بعداً تمام کنید.",
+ "saveServer": "ذخیرهٔ سرور",
+ "savingServer": "در حال ذخیره…",
+ "toastIpRequired": "آدرس IP سرور الزامی است",
+ "toastPasswordSwitchRequired": "هنگام تغییر به احراز هویت رمز عبور، رمز الزامی است",
+ "toastKeyPathSwitchRequired": "هنگام تغییر به احراز هویت کلید SSH، مسیر کلید الزامی است",
+ "toastKeyRequired": "کلید خصوصی الزامی است",
+ "toastUpdated": "سرور بهروز شد",
+ "toastSaved": "سرور ذخیره شد",
+ "toastSaveFailed": "ذخیرهٔ سرور ناموفق بود",
+ "toastPasswordTestRequired": "برای آزمایش اتصال رمز عبور الزامی است",
+ "toastKeyPathTestRequired": "برای آزمایش اتصال مسیر کلید الزامی است",
+ "toastConnectionSuccess": "اتصال موفق بود",
+ "toastConnectionFailed": "اتصال ناموفق بود",
+ "toastConnectionTestFailed": "آزمایش اتصال ناموفق بود"
+ },
+ "detail": {
+ "tabs": {
+ "overview": "نمای کلی",
+ "migrations": "مهاجرتها",
+ "components": "اجزا",
+ "github": "احراز هویت Git",
+ "security": "امنیت",
+ "ports": "ارسال پورت",
+ "terminal": "ترمینال"
+ },
+ "healthy": "سالم",
+ "issues": "مشکلات",
+ "sshPort": "SSH · {port}",
+ "edit": "ویرایش",
+ "removeServer": "حذف سرور",
+ "serverNotFound": "سرور پیدا نشد",
+ "serverNotFoundDesc": "این سرور در فضای کار شما نیست، یا به آن دسترسی ندارید. ممکن است حذف شده باشد.",
+ "noServerConfigured": "سروری با شناسه «{id}» پیکربندی نشده.",
+ "editServer": "ویرایش سرور",
+ "editSubtitle": "بهروزرسانی جزئیات اتصال برای {name}",
+ "saveChanges": "ذخیرهٔ تغییرات",
+ "connection": "اتصال",
+ "host": "میزبان",
+ "port": "پورت",
+ "user": "کاربر",
+ "auth": "احراز هویت",
+ "authSshKey": "کلید SSH",
+ "authPassword": "رمز عبور",
+ "cancel": "لغو",
+ "remove": "حذف",
+ "removeServerMessage": "مطمئنید؟ این همهٔ اعتبار SSH و تنظیمات اتصال را پاک میکند.",
+ "removeComponentTitle": "حذف {label}",
+ "removeOpenrestyMessage": "این کانتینر لبه را از سرور حذف میکند. گواهیها و vhostها روی میزبان میمانند. وقتی فقط نیاز به تازهسازی لبه به ایمیج فعلی دارید از نصب مجدد استفاده کنید.",
+ "removeComponentMessage": "این {label} را از سرور حذف میکند. مخرب است و ممکن است ویژگیهای استقرار مرتبط را تا نصب دوباره غیرفعال کند.",
+ "reinstallDockerTitle": "Docker روی این سرور دوباره نصب شود؟",
+ "reinstallDockerMessage": "این نصبکنندهٔ رسمی Docker را اجرا میکند که موتور فعلی را میکشد — روی سرور بهروز این یک ارتقای عمده است. نصب یا ارتقای Docker دیمون را راهاندازی مجدد میکند، و آن هر کانتینر روی این سرور را راهاندازی مجدد میکند، از جمله هر چیزی که Openship اینجا اجرا میکند. هیچ چیز دیگری Docker را برایتان نصب نمیکند: گامهای دیگر تا وقتی کار میکند آن را رد میکنند.",
+ "removalTakingLonger": "حذف بیشتر از انتظار طول کشیده. وضعیت سرور را بررسی کنید.",
+ "removalTimedOutError": "حذف زمانپر شد — برای دیدن وضعیت فعلی سرور را دوباره بررسی کنید.",
+ "toastComponentActionCompleted": "عمل جزء کامل شد",
+ "toastSomeActionsFailed": "بعضی اعمال جزء ناموفق بودند",
+ "toastHealthCheckFailedAfterInstall": "بررسی سلامت بعد از نصب ناموفق بود",
+ "toastHealthCheckFailed": "بررسی سلامت ناموفق بود",
+ "toastNoInstallableMissing": "جزء قابل نصب گمشدهای نیست",
+ "toastServerMissing": "سرور موجود نیست",
+ "toastFailedStartInstall": "شروع نصب ناموفق بود",
+ "toastFailedRun": "اجرای {label} ناموفق بود",
+ "toastComponentRemoved": "{label} حذف شد",
+ "toastRemovalTimedOut": "حذف زمانپر شد — وضعیت سرور را دوباره بررسی کنید",
+ "toastFailedRemove": "حذف {label} ناموفق بود",
+ "toastServerRemoved": "سرور حذف شد",
+ "toastFailedRemoveServer": "حذف سرور ناموفق بود"
+ },
+ "banner": {
+ "unreachableTitle": "نمیتوان به {host} رسید",
+ "unreachableBody": "Openship نتوانست اتصال SSH به {host}:{port} باز کند. مطمئن شوید سرور روشن است و sshd روی پورت {port} اتصال میپذیرد. اگر پشت فایروال یا NAT است، تأیید کنید میزبان از این ماشین در دسترس است.",
+ "authTitle": "اعتبار SSH رد شد",
+ "authBody": "به {host}:{port} رسیدیم اما سرور احراز هویت را رد کرد. کلید یا رمز ذخیرهشده برای این سرور با آنچه در ~/.ssh/authorized_keys میزبان مجاز است جور نیست.",
+ "noServerTitle": "پیکربندی سرور ناقص است",
+ "noServerBody": "این ورودی سرور تنظیمات SSH ندارد. ویرایش را باز کنید تا میزبان، کاربر و اعتبار را تنظیم کنید، سپس بررسی سلامت اجرا کنید.",
+ "hostChannelTitle": "Openship نمیتواند به میزبان این ماشین برسد",
+ "hostChannelBody": "Openship اینجا در یک کانتینر اجرا میشود و از طریق SSH در {target} با میزبان حرف میزند. آنجا کسی پاسخ نداد، پس نمیتواند عملیات سطح میزبان را روی این جعبه اجرا کند.",
+ "hostChannelImpact": "استقرارهای معمولی به این جعبه همچنان کار میکنند — از سوکت Docker میروند. تا وقتی کانال کار کند مسدود است: ترمینال میزبان، اطلاعات سیستم میزبان و اسکن پورت میزبان؛ تحویل گرفتن :80/:443 از پروکسیای که Openship شروع نکرده؛ نصب یا بهروزرسانی موتور ایمیل؛ استقرار اپ کاتالوگ که به فایل پیکربندی تولیدشده روی میزبان نیاز دارد (مثلاً kong.yml سوپابیس). این جعبه همچنین تا وقتی کانال پایین است در فهرست سرور داشبورد «آفلاین» خوانده میشود.",
+ "hostChannelFix": "این را روی میزبان اجرا کنید، سپس دوباره تلاش کنید:",
+ "hostChannelMissingTitle": "Openship کانالی به میزبان این ماشین ندارد",
+ "hostChannelMissingBody": "Openship اینجا در یک کانتینر اجرا میشود، و کانال SSH که برای رسیدن به میزبان استفاده میکند هرگز فراهم نشده. آدرسی برای تماس نبود، پس اینجا چیزی پایین نیست — عملیات سطح میزبان روی این جعبه فقط جایی برای اجرا ندارند.",
+ "hostChannelWouldUse": "پس از فراهمسازی کانال در {target} به میزبان میرسد. هنوز کسی با آن آدرس تماس نگرفته.",
+ "hostChannelProvisionFix": "این را روی میزبان اجرا کنید تا کانال فراهم شود، سپس دوباره تلاش کنید:",
+ "hostChannelAuthTitle": "میزبان کلید Openship را رد کرد",
+ "hostChannelAuthBody": "Openship اینجا در یک کانتینر اجرا میشود و از طریق SSH در {target} به میزبان میرسد. میزبان پاسخ داد و سپس کلید را رد کرد، پس عملیات سطح میزبان نمیتوانند اجرا شوند. یا کلید دیگر برای آن حساب مجاز نیست، یا sshd اصلاً اجازهٔ ورود آن حساب را نمیدهد. اعتبار SSH ذخیرهشده روی این سرور برای این اتصال استفاده نمیشود — کانال کلید خودش را دارد.",
+ "hostChannelReauthorizeFix": "این را روی میزبان اجرا کنید تا کلید کانال دوباره مجاز شود، سپس دوباره تلاش کنید:",
+ "unknownTitle": "بررسی سلامت ناموفق بود",
+ "unknownBody": "Openship نتوانست با این سرور حرف بزند.",
+ "checkPowered": "آیا VPS / VM روشن است؟",
+ "pingSuffix": "از این ماشین — پاسخ میدهد؟",
+ "ncSuffix": "— آیا پورت {port} باز است؟",
+ "checking": "در حال بررسی…",
+ "retry": "تلاش دوباره",
+ "editCredentials": "ویرایش اعتبارنامه",
+ "editServer": "ویرایش سرور",
+ "showRawError": "نمایش خطای خام"
+ },
+ "overview": {
+ "cpu": "CPU",
+ "memory": "حافظه",
+ "disk": "دیسک",
+ "uptime": "مدت روشن بودن",
+ "load": "بار {load1} · {load5} · {load15}",
+ "usageOf": "{used} از {total}",
+ "sinceLastBoot": "از آخرین بوت",
+ "components": "اجزا",
+ "checking": "در حال بررسی…",
+ "allOperational": "همهٔ سیستمها عملیاتیاند",
+ "unhealthyOf": "{unhealthy} از {total} ناسالم",
+ "noData": "دادهای نیست",
+ "healthy": "سالم",
+ "unhealthy": "ناسالم",
+ "noHealthData": "هنوز دادهٔ سلامتی نیست"
+ },
+ "components": {
+ "reinstall": "نصب مجدد",
+ "install": "نصب",
+ "running": "در حال اجرا…",
+ "unsupported": "پشتیبانینشده",
+ "remove": "حذف",
+ "systemHealth": "سلامت سیستم",
+ "requiredComponents": "اجزای الزامی و سرویسها",
+ "roleDocker": "زمان اجرای کانتینر",
+ "roleGit": "کنترل نسخه",
+ "roleOpenresty": "پروکسی معکوس",
+ "roleCertbot": "گواهیهای SSL",
+ "roleRsync": "انتقال فایل",
+ "installMissing": "نصب موارد گمشده ({count})",
+ "allInstalled": "همه نصب شده",
+ "installing": "در حال نصب…",
+ "removing": "در حال حذف…",
+ "checking": "در حال بررسی…",
+ "recheck": "بررسی دوباره",
+ "runningHealthChecks": "در حال اجرای بررسیهای سلامت…",
+ "noHealthData": "هنوز دادهٔ سلامتی نیست",
+ "detectedInfrastructure": "زیرساخت تشخیصدادهشده",
+ "removalComplete": "حذف کامل شد",
+ "removalFinishedErrors": "حذف با خطا تمام شد",
+ "installComplete": "نصب کامل شد",
+ "installFinishedErrors": "نصب با خطا تمام شد",
+ "removalLogs": "گزارش حذف",
+ "installLogs": "گزارش نصب",
+ "componentsCount": "{completed}/{total} جزء",
+ "dismiss": "نادیده گرفتن",
+ "statusInstalling": "در حال نصب…",
+ "statusRemoving": "در حال حذف…",
+ "statusInstalled": "نصبشده",
+ "statusRemoved": "حذفشده",
+ "statusFailed": "ناموفق",
+ "statusWaiting": "در انتظار"
+ },
+ "ports": {
+ "title": "ارسال پورت",
+ "descBefore": "مثل VS Code: به پورت سرور راه دور در",
+ "descAfter": " روی این ماشین برسید. فقط به loopback بسته شده، و فقط وقتی ارسال فعال است.",
+ "loading": "در حال بارگذاری…",
+ "noForwards": "هنوز ارسالی نیست.",
+ "running": "در حال اجرا",
+ "stopped": "متوقف",
+ "stop": "توقف",
+ "start": "شروع",
+ "remove": "حذف",
+ "openOnStartup": "باز کردن هنگام شروع",
+ "ariaOpenThisForward": "این ارسال را هنگام شروع باز کن",
+ "ariaOpenNewForward": "ارسال جدید را هنگام شروع باز کن",
+ "remotePortPlaceholder": "پورت راه دور",
+ "localPortPlaceholder": "محلی (خودکار)",
+ "add": "افزودن",
+ "toastRemotePortRange": "پورت راه دور باید بین ۱ و ۶۵۵۳۵ باشد",
+ "toastLocalPortRange": "پورت محلی باید ۰ (خودکار) یا ۱–۶۵۵۳۵ باشد"
+ },
+ "security": {
+ "title": "محدودیت نرخ",
+ "subtitle": "محدودیت نرخ درخواست OpenResty برای این سرور",
+ "loading": "در حال بارگذاری…",
+ "couldntRead": "نتوانستیم پیکربندی محدودیت نرخ OpenResty را بخوانیم",
+ "retryHint": "بعد از تأیید نصب و در دسترس بودن OpenResty روی این سرور دوباره تلاش کنید.",
+ "retry": "تلاش دوباره",
+ "refresh": "تازهسازی",
+ "loadedFromLive": "از پیکربندی زندهٔ OpenResty بارگذاری شد. برای تغییر ویرایش را بزنید.",
+ "edit": "ویرایش",
+ "remove": "حذف",
+ "editLiveHint": "محدودیت نرخ زندهٔ OpenResty این سرور را ویرایش کنید.",
+ "noLimitHint": "هنوز محدودیت نرخی فعال نیست. برای ساخت یکی، درخواست در ثانیه را تنظیم کنید.",
+ "resetDraft": "بازنشانی پیشنویس",
+ "cancel": "لغو",
+ "requestsPerSecond": "درخواست / ثانیه",
+ "burstAuto": "انفجار بهصورت خودکار محاسبه میشود: {burst}",
+ "burstCustom": "بازنویسی انفجار سفارشی فعال است: {burst}",
+ "advanced": "پیشرفته",
+ "advancedHint": "وقتی کنترل دقیقتری میخواهید محاسبهٔ خودکار انفجار را بازنویسی کنید.",
+ "hideAdvanced": "پنهان کردن پیشرفته",
+ "showAdvanced": "نمایش پیشرفته",
+ "burstMode": "حالت انفجار",
+ "burstModeHint": "خودکار انفجار را با درخواست در ثانیه همگام نگه میدارد. دستی اجازهٔ بازنویسی میدهد.",
+ "switchToManual": "تغییر به دستی",
+ "useAutomaticBurst": "استفاده از انفجار خودکار",
+ "burstAllowance": "سهمیهٔ انفجار",
+ "burstAllowanceHint": "درخواستهای اضافه صفشده با nodelay.",
+ "whitelistedIps": "IPهای مجاز",
+ "whitelistHint": "این IPها از محدودیت نرخ عبور میکنند. Loopback (127.0.0.1، ::1) همیشه مجاز است.",
+ "ipPlaceholder": "مثلاً 145.223.101.50 یا 10.0.0.0/8",
+ "add": "افزودن",
+ "draftChanges": "تغییرات پیشنویس: {summary}",
+ "noDraftChanges": "تغییر پیشنویسی نیست",
+ "removeRateLimit": "حذف محدودیت نرخ",
+ "saveChanges": "ذخیرهٔ تغییرات",
+ "applyRateLimit": "اعمال محدودیت نرخ",
+ "policyBase": "{rps} درخواست/ثانیه، انفجار {burst}",
+ "policyWhitelisted": "{base}، {count} مجاز",
+ "failedReadConfig": "خواندن پیکربندی OpenResty ناموفق بود",
+ "toastRateLimitUpdated": "محدودیت نرخ بهروز شد: {from} -> {to}",
+ "toastFailedApply": "اعمال ناموفق بود",
+ "toastFailedSave": "ذخیرهٔ تنظیمات محدودیت نرخ ناموفق بود",
+ "toastRemoved": "محدودیت نرخ حذف شد",
+ "toastFailedRemove": "حذف محدودیت نرخ ناموفق بود",
+ "toastInvalidIp": "قالب IP/CIDR نامعتبر",
+ "toastAlreadyWhitelisted": "از قبل در فهرست مجاز است",
+ "ports": {
+ "title": "در معرض بودن پورت",
+ "subtitle": "ببینید کدام پورتهای در حال گوش از شبکه در دسترساند",
+ "introHint": "هر پورتی که روی این ماشین گوش میدهد را اسکن کنید و آنهایی را که به رابط عمومی بهجای loopback بستهاند علامت بزنید.",
+ "scanButton": "اسکن پورتها",
+ "scanning": "در حال اسکن پورتهای در حال گوش…",
+ "rescan": "اسکن دوباره",
+ "retry": "تلاش دوباره",
+ "scanFailed": "نتوانستیم پورتها را اسکن کنیم",
+ "scanFailedHint": "ممکن است سرور در دسترس نباشد. وقتی برگشت دوباره تلاش کنید.",
+ "summary": "{total} پورت در حال گوش · {exposed} در معرض شبکه",
+ "summaryZeroExposed": "{total} پورت در حال گوش · هیچکدام در معرض شبکه نیست",
+ "exposed": "در معرض",
+ "loopback": "فقط محلی",
+ "noProcessInfo": "نام فرآیند در دسترس نیست (ss روی این میزبان نصب نیست).",
+ "unknownProcess": "فرآیند ناشناخته",
+ "empty": "پورت در حال گوشی پیدا نشد.",
+ "inconclusive": "اسکن بینتیجه",
+ "inconclusiveHint": "نتوانستیم جدول سوکت این میزبان را بخوانیم. دوباره تلاش کنید، یا دسترسی SSH را بررسی کنید.",
+ "reachable": "در دسترس",
+ "firewalled": "فایروالشده",
+ "expectedOpen": "انتظار میرود باز باشد",
+ "sensitiveHint": "نباید رو به اینترنت باشد",
+ "sensitiveReachable": "سرویس حساس از اینترنت در دسترس است",
+ "reachableLegend": "«در دسترس» با شمارهگیری پورت از بیرون جعبه تأیید شد؛ «فایروالشده» بسته شده اما مسدود است (مثلاً توسط فایروال ابری).",
+ "summaryReachable": "{total} پورت در حال گوش · {reachable} از اینترنت در دسترس · {firewalled} مسدودشده توسط فایروال"
+ }
+ },
+ "terminal": {
+ "title": "ترمینال",
+ "description": "شلهای SSH زنده روی WebSocket. تا ۳ همزمان — کپی با انتخاب."
+ },
+ "setup": {
+ "editServer": "ویرایش سرور",
+ "addServer": "افزودن سرور",
+ "enterDetails": "جزئیات سرور را وارد کنید و روش احراز هویت را انتخاب کنید",
+ "gettingStarted": "شروع کار",
+ "whatYouNeed": "چه چیزی نیاز دارید",
+ "needServer": "سروری با دسترسی SSH (اوبونتو، دبیان، یا مشابه)",
+ "needAuth": "کلید SSH یا رمز عبور برای احراز هویت",
+ "needChecks": "بعد از ذخیره، Openship بررسیها را اجرا میکند و پیشنیازهای تعریفشدهٔ بکاند گمشده را برایتان نصب میکند.",
+ "yourServer": "سرور شما",
+ "serverSetup": "راهاندازی سرور",
+ "subChoose": "انتخاب کنید چگونه سرور را راهاندازی کنید",
+ "subChecking": "در حال بررسی الزامات سرور…",
+ "subResults": "نتایج برای {host}",
+ "subInstalling": "در حال نصب اجزا…",
+ "autoTitle": "راهاندازی خودکار",
+ "autoDesc": "الزامات را بررسی کنید و همه چیز را خودکار نصب کنید. یک کلیک — Openship کار Docker، OpenResty و Git را انجام میدهد.",
+ "stepTitle": "راهاندازی گامبهگام",
+ "stepDesc": "هر جزء را بازبینی کنید و انتخاب کنید چه نصب شود. قبل از ادامه وضعیت جزئی را ببینید.",
+ "skipTitle": "فعلاً رد کن",
+ "skipDesc": "سرور را بدون نصب چیزی اضافه کنید — فقط سلامت را پایش کنید. بعداً از صفحهٔ سرور راهاندازی را اجرا کنید.",
+ "connecting": "در حال اتصال به سرور…",
+ "runningChecks": "اجرای بررسیهای سلامت سیستم از طریق SSH",
+ "connectionFailed": "اتصال ناموفق بود",
+ "installing": "در حال نصب…",
+ "installationFailed": "نصب ناموفق بود",
+ "ready": "آماده",
+ "missing": "گمشده",
+ "stepConnect": "اتصال",
+ "stepCheck": "بررسی",
+ "stepInstall": "نصب",
+ "statusInstalling": "در حال نصب…",
+ "statusDone": "انجام شد",
+ "statusFailed": "ناموفق",
+ "statusWaiting": "در انتظار",
+ "overallProgress": "پیشرفت کلی",
+ "connectingChecking": "در حال اتصال به {host} و بررسی الزامات…",
+ "allComponentsInstalled": "همهٔ اجزا نصب شدند",
+ "setupFinishedErrors": "راهاندازی با خطا تمام شد",
+ "installingComponents": "در حال نصب اجزا",
+ "installedOf": "{count} از {total} روی {host} نصب شد",
+ "settingUp": "در حال راهاندازی {host}…",
+ "allRequirementsMetTitle": "همهٔ الزامات برآورده شد",
+ "allRequirementsMet": "همهٔ الزامات برآورده شد",
+ "readyToDeploy": "{host} آمادهٔ استقرار است",
+ "installLogs": "گزارش نصب",
+ "line": "خط",
+ "lines": "خط",
+ "waitingForOutput": "در انتظار خروجی…",
+ "doneGoToServers": "انجام شد — رفتن به سرورها",
+ "goToServers": "رفتن به سرورها",
+ "retryFailed": "تلاش دوباره برای ناموفقها ({count})",
+ "setupComplete": "راهاندازی کامل شد",
+ "installAllMissing": "نصب همهٔ موارد گمشده",
+ "installMissingComponents": "نصب اجزای گمشده",
+ "recheck": "بررسی دوباره",
+ "needAttention": "{count} جزء نیاز به توجه دارد",
+ "detectedInfrastructure": "زیرساخت تشخیصدادهشده",
+ "toastSetupCompleted": "راهاندازی سرور کامل شد",
+ "toastSomeComponentsFailed": "بعضی اجزا نصب نشدند",
+ "toastSaveBeforeSetup": "قبل از اجرای راهاندازی سرور را ذخیره کنید",
+ "toastHealthCheckFailed": "بررسی سلامت ناموفق بود",
+ "toastSaveBeforeInstall": "قبل از نصب اجزا سرور را ذخیره کنید",
+ "toastAlreadyReady": "سرور از قبل آماده است",
+ "toastFailedStartInstall": "شروع نصب ناموفق بود"
+ },
+ "toastTitles": {
+ "server": "سرور",
+ "serverSetup": "راهاندازی سرور",
+ "serverCheck": "بررسی سرور",
+ "security": "امنیت",
+ "portForwarding": "ارسال پورت"
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/fa/settings.json b/apps/dashboard/src/i18n/locales/fa/settings.json
new file mode 100644
index 000000000..5b9731722
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/fa/settings.json
@@ -0,0 +1,1022 @@
+{
+ "language": {
+ "title": "زبان",
+ "description": "زبانی را که در سراسر داشبورد استفاده میشود انتخاب کنید."
+ },
+ "productView": {
+ "title": "رابط",
+ "description": "این داشبورد کدام صفحهٔ کنترل را به شما نشان میدهد.",
+ "platformLabel": "پلتفرم کامل",
+ "platformDesc": "پروژهها، اپها، استقرارها، سرورها — هر کاری که این نصب میتواند انجام دهد.",
+ "mailLabel": "فقط ایمیل",
+ "mailDesc": "صفحهٔ کنترل ایمیل در نوار کناری: دامنهها، صندوقهای پستی، DNS، صف، گزارشها.",
+ "instanceDefault": "این نمونه بهصورت پیشفرض روی {mode} است.",
+ "useDefault": "استفاده از پیشفرض نمونه"
+ },
+ "mailMode": {
+ "title": "حالت محصول",
+ "description": "این نمونه خود را چگونه معرفی میکند. این پیشفرض برای همهٔ اینجاست — هر کاربر همچنان میتواند نمای خودش را در تنظیمات → عمومی انتخاب کند.",
+ "platformLabel": "پلتفرم کامل",
+ "platformDesc": "پروژهها، اپها، استقرارها، سرورها — هر کاری که این نصب میتواند انجام دهد.",
+ "mailLabel": "Openship Mail",
+ "mailDesc": "نمونه را بهعنوان Openship Mail برند میکند و نوار کناری را به صفحهٔ کنترل ایمیل تبدیل میکند.",
+ "savedOverridden": "ذخیره شد. نمای خودتان تغییر نکرد — آن را در تنظیمات → عمومی انتخاب کردهاید.",
+ "errorToast": "تغییر حالت محصول ممکن نشد.",
+ "envNote": "این نمونه از OPENSHIP_PRODUCT=mail در محیط پیروی میکند. انتخاب یک حالت اینجا آن را لغو میکند."
+ },
+ "common": {
+ "cancel": "لغو",
+ "save": "ذخیره",
+ "done": "انجام شد",
+ "close": "بستن",
+ "back": "بازگشت",
+ "disconnect": "قطع اتصال",
+ "remove": "حذف",
+ "create": "ساخت",
+ "previous": "قبلی",
+ "next": "بعدی",
+ "loading": "در حال بارگذاری…",
+ "copy": "کپی",
+ "copied": "کپی شد",
+ "toast": {
+ "cloud": "کلاد",
+ "defaults": "پیشفرضها",
+ "settings": "تنظیمات",
+ "dns": "DNS",
+ "notifications": "اعلانها",
+ "team": "تیم",
+ "members": "اعضا",
+ "invitation": "دعوتنامه",
+ "invitations": "دعوتنامهها",
+ "permissions": "مجوزها",
+ "accessTokens": "توکنهای دسترسی",
+ "cloneCredentials": "اعتبارنامههای کلون",
+ "migration": "مهاجرت",
+ "switchBack": "بازگشت به حالت قبل",
+ "export": "خروجی",
+ "import": "ورود",
+ "importComplete": "ورود کامل شد",
+ "authUpgrade": "ارتقای احراز هویت",
+ "disconnect": "قطع اتصال"
+ }
+ },
+ "page": {
+ "title": "تنظیمات",
+ "subtitle": "ترجیحات، تیم و پیکربندی نمونه را مدیریت کنید",
+ "cloudConnectedToast": "به Openship Cloud متصل شد"
+ },
+ "sidebar": {
+ "title": "تنظیمات",
+ "tabs": {
+ "general": "عمومی",
+ "git": "Git",
+ "tokens": "توکنهای API",
+ "mcp": "MCP",
+ "team": "تیم",
+ "notifications": "اعلانها",
+ "email": "ایمیل",
+ "systemSender": "فرستندهٔ سیستم",
+ "credentials": "اعتبارنامهها",
+ "cloud": "کلاد",
+ "infrastructure": "زیرساخت",
+ "instance": "نمونه"
+ }
+ },
+ "email": {
+ "title": "ایمیل (SMTP)",
+ "description": "سرویس SMTP که این نمونه از طریق آن ایمیل میفرستد",
+ "intro": "برای بازنشانی رمز عبور، تأیید ایمیل، دعوت تیم و اعلانها استفاده میشود. فقط اعتبارنامهٔ SMTP هر ارائهدهندهای را بچسبانید — Amazon SES، Gmail، Mailgun، یا رلهٔ سازمانی — میزبان، پورت، نام کاربری، رمز عبور. سروری برای راهاندازی نیست؛ Openship مستقیماً به آن وصل میشود. رمز عبور رمزنگاری میشود و دیگر نمایش داده نمیشود.",
+ "loading": "در حال بارگذاری…",
+ "configuredBadge": "پیکربندیشده",
+ "notConfigured": "هنوز راهاندازی نشده — بازنشانی رمز، دعوتها و اعلانهای ایمیلی تا وقتی اعتبارنامهٔ SMTP را در زیر وارد نکنید خاموش میمانند.",
+ "providerLabel": "ارائهدهنده",
+ "hostLabel": "میزبان SMTP",
+ "hostPlaceholder": "smtp.example.com",
+ "portLabel": "پورت",
+ "userLabel": "نام کاربری",
+ "userPlaceholder": "user@example.com",
+ "passwordLabel": "رمز عبور",
+ "passwordPlaceholder": "رمز SMTP یا کلید اپ",
+ "passwordKeepPlaceholder": "•••••••• (برای نگه داشتن فعلی خالی بگذارید)",
+ "fromLabel": "آدرس فرستنده",
+ "fromPlaceholder": "Openship ",
+ "fromHint": "اختیاری. پیشفرض نام کاربری است.",
+ "fromHintRequired": "لازم است — نام کاربری شما ایمیل نیست، پس گیرندگان به یک آدرس From واقعی نیاز دارند.",
+ "fromRequired": "یک آدرس From وارد کنید — نام کاربری این ارائهدهنده ایمیل نیست، پس نمیتواند فرستنده باشد.",
+ "fromInvalid": "آدرس From باید یک ایمیل معتبر داشته باشد (مثلاً Name ).",
+ "save": "ذخیره",
+ "saving": "در حال ذخیره…",
+ "saved": "تنظیمات SMTP ذخیره شد",
+ "saveFailed": "ذخیرهٔ تنظیمات SMTP ممکن نشد",
+ "hostRequired": "میزبان SMTP را وارد کنید.",
+ "portInvalid": "پورت باید عددی بین ۱ تا ۶۵۵۳۵ باشد.",
+ "testTitle": "ارسال ایمیل آزمایشی",
+ "testPlaceholder": "you@example.com",
+ "sendTest": "ارسال آزمایش",
+ "sending": "در حال ارسال…",
+ "testSent": "ایمیل آزمایشی ارسال شد — صندوق ورودی را بررسی کنید.",
+ "testFailed": "آزمایش ناموفق بود",
+ "saveFirst": "قبل از ارسال آزمایش، تنظیمات SMTP را ذخیره کنید.",
+ "disable": "غیرفعالسازی و پاککردن SMTP",
+ "disabled": "SMTP غیرفعال شد",
+ "toastTitle": "ایمیل"
+ },
+ "github": {
+ "title": "GitHub",
+ "titleWithLogin": "GitHub · @{login}",
+ "noInstallations": "متصل · هنوز نصبی نیست",
+ "connectedOne": "متصل · ۱ نصب",
+ "connectedMany": "متصل · {count} نصب",
+ "requiresCloud": "نیاز به Openship Cloud دارد — اپ متعلق به openship.io است",
+ "connectPrompt": "حساب GitHub خود را برای استقرار مخزنها وصل کنید",
+ "checkingConnection": "در حال بررسی اتصال…",
+ "addAccount": "افزودن حساب",
+ "installApp": "نصب GitHub App",
+ "manageOnGithub": "مدیریت در GitHub",
+ "disconnect": "قطع اتصال",
+ "disconnectTitle": "قطع اتصال {label}",
+ "disconnectAppLabel": "اپ GitHub مربوط به Openship",
+ "disconnectAppBody": "این ردیف حساب OAuth مربوط به Openship را حذف میکند. نصب GitHub App تا وقتی آن را در GitHub حذف نکنید باقی میماند.",
+ "cloudExplainer": "Openship GitHub App متعلق به openship.io است. نمونهٔ خود را به Openship Cloud وصل کنید تا از توکنهای نصب محدود برای کلون مخزنهای خصوصی استفاده کنید (برای استقرار محلی و راه دور کار میکند).",
+ "connectCloud": "اتصال Openship Cloud",
+ "noCloudTitle": "کلون مخزنهای خصوصی بدون کلاد",
+ "useSshPerServer": "کلید SSH برای هر سرور",
+ "usePat": "توکن دسترسی",
+ "noCloudHint": "یک کلید SSH روی هر سرور پیکربندی کنید (سرورها → GitHub)، یا یک توکن دسترسی شخصی اضافه کنید که برای استقرار محلی و راه دور کار کند.",
+ "linkExplainer": "حساب GitHub خود را وصل کنید تا مخزنها را وارد کنید، استقرار خودکار هنگام push را فعال کنید و شاخهها را مستقیم از داشبورد مدیریت کنید.",
+ "connecting": "در حال اتصال…",
+ "connect": "اتصال GitHub",
+ "orgBadge": "سازمان",
+ "userBadge": "کاربر",
+ "ghCli": {
+ "title": "gh CLI",
+ "loggedInAs": "وارد شده بهعنوان @{login}",
+ "fallbackDesc": "پشتیبان اختیاری ساخت محلی برای مخزنهای خارج از نصبهای App شما",
+ "localOnly": "فقط ساختهای محلی",
+ "localOnlyTitle": "در زمان استقرار برای ساخت روی سرور راه دور رد میشود. برای استقرار راه دور، App یا توکن کلون بهازای پروژه لازم است.",
+ "usedForDeploys": "برای استقرارها استفاده میشود",
+ "usedForDeploysTitle": "اتصال کلاد نیست — در حال حاضر gh CLI منبع اصلی برای همه چیز است",
+ "activeWarnStrong": "gh CLI منبع فعال است.",
+ "activeWarnRest": "استقرار روی سرورهای راه دور رد میشود — یک کلید SSH بهازای سرور اضافه کنید، Openship App را وصل کنید، یا یک توکن بهازای پروژه تنظیم کنید تا به مقصدهای راه دور استقرار دهید.",
+ "primaryNotePrefix": "استقرارهای راه دور از احراز هویت GitHub خود هر سرور (کلید SSH یا توکن) استفاده میکنند، یا از Openship App برای کلاد. gh CLI فقط پوشش میدهد",
+ "primaryNoteStrong": "ساختهای محلی",
+ "primaryNoteSuffix": "روی مخزنهای خارج از نصبهای App شما.",
+ "optionalPrefix": "اختیاری. اجرا کنید",
+ "optionalSuffix": "روی این ماشین تا کلون ساخت محلی مخزنهای خارج از نصبهای App فعال شود. استقرارهای راه دور از کلید SSH هر سرور یا App استفاده میکنند.",
+ "disconnectPrefix": "برای قطع اتصال، اجرا کنید",
+ "disconnectSuffix": "در ترمینال خود.",
+ "use": "استفاده از gh CLI",
+ "login": "ورود با GitHub",
+ "deviceHint": "این کد یکبارمصرف را در GitHub وارد کنید تا این نمونه را مجاز کنید:",
+ "waiting": "در انتظار مجوز — این بهصورت خودکار بهروز میشود.",
+ "tokenAltPrefix": "توکن را ترجیح میدهید؟ یک توکن دسترسی شخصی اضافه کنید در",
+ "tokenAltLink": "اعتبارنامههای کلون.",
+ "forwardOnNote": "ارسال روشن است — gh CLI کلونهای سرور راه دور را هم احراز هویت میکند. هویت شما برای هر کلون از روی SSH تونل میشود؛ چیزی روی سرور ذخیره نمیشود.",
+ "forwardOffHint": "فعلاً فقط ساختهای محلی. «هویت git من را به سرورهای ساخت بفرست» را روشن کنید تا gh CLI برای استقرار روی سرور راه دور هم استفاده شود.",
+ "manageForward": "مدیریت ارسال",
+ "disconnectBody": "Openship این اعتبارنامه را از این نمونه حذف میکند و دیگر برای کلونها از آن استفاده نمیکند. تا وقتی در GitHub لغوش نکنید آنجا معتبر میماند. هر زمان میتوانید دوباره وصل شوید.",
+ "recheck": "بررسی دوباره"
+ },
+ "activeVia": "متصل از طریق {method}",
+ "pickMethod": "انتخاب کنید Openship چگونه به مخزنهای شما برسد.",
+ "methodDevice": "ورود GitHub",
+ "methodApp": "Openship Cloud GitHub App",
+ "methodAppDesc": "App مدیریتشده — استقرار روی سرورهای راه دور با توکنهای کوتاهعمر.",
+ "methodSshDesc": "یک کلید استقرار به هر سرور وصل کنید (سرورها → GitHub).",
+ "methodTokenDesc": "یک توکن دسترسی شخصی، قابل استفاده همهجا.",
+ "signIn": "ورود با GitHub",
+ "signInDesc": "ورود مرورگری با کد دستگاه. چیزی برای ثبت نیست، نیازی به SSH به سرور نیست.",
+ "changeMethod": "تغییر روش",
+ "otherMethods": "روشهای دیگر",
+ "openGithub": "باز کردن GitHub",
+ "copyCode": "برای کپی کلیک کنید",
+ "tokenConnect": "اتصال",
+ "tokenCreate": "ساخت توکن در GitHub",
+ "tokenGhHint": "از قبل روی سرور وارد شدهاید با",
+ "tokenSaveFailed": "ذخیرهٔ توکن ممکن نشد.",
+ "forwardOffHint": "فقط ساختهای محلی. «هویت git من را به سرورهای ساخت بفرست» را روشن کنید تا برای استقرار روی سرور راه دور هم استفاده شود.",
+ "remoteCredentialHint": "برای مرور مخزن و ساختهایی که روی این میزبان اجرا میشوند. استقرارهایی که روی سرور راه دور ساخته میشوند از اعتبارنامهٔ همان سرور استفاده میکنند — یک کلید استقرار یا توکن بهازای هر سرور اضافه کنید.",
+ "methodToken": "توکن GitHub",
+ "methodHostCli": "gh CLI روی این میزبان",
+ "manageTokensOnGithub": "مدیریت توکنها در GitHub",
+ "manageAccessOnGithub": "مدیریت دسترسی در GitHub",
+ "disconnectScopeNote": "قطع اتصال این اعتبارنامه را از Openship حذف میکند و دیگر برای کلونها از آن استفاده نمیشود. تا وقتی در GitHub لغوش نکنید آنجا معتبر میماند.",
+ "credentialRejected": "GitHub {method} ذخیرهشده را رد کرد.",
+ "credentialRejectedImpact": "لغو شده، منقضی شده، یا یک محدودهٔ لازم را از دست داده است. استقرارهایی که با آن کلون میکنند تا وقتی دوباره در زیر وصل نشوید ناموفق میمانند.",
+ "credentialUnreachable": "برای بررسی {method} ذخیرهشده به GitHub نرسیدیم.",
+ "credentialUnreachableImpact": "معمولاً مشکل شبکه است نه اعتبارنامهٔ خراب — Openship به استفاده از آن ادامه میدهد. اگر GitHub در دسترس نباشد، اتصال دوباره کمکی نمیکند.",
+ "credentialCheckedAt": "آخرین بررسی {time}"
+ },
+ "deployDefaults": {
+ "title": "پیشفرضهای استقرار",
+ "description": "استقرارهای جدید بهصورت پیشفرض کجا فرود بیایند",
+ "loading": "در حال بارگذاری ترجیحات…",
+ "intro": "مقصد انتخابشده در انتخابگر استقرار از پیش انتخاب میشود. همچنان میتوانید آن را برای هر استقرار عوض کنید، یا پاک کنید تا هر بار پرسیده شود.",
+ "defaultServer": "سرور پیشفرض",
+ "noServers": "هنوز سروری پیکربندی نشده — یکی را از انتخابگر استقرار اضافه کنید.",
+ "clearDefault": "پاک کردن پیشفرض (هر بار بپرس)",
+ "targets": {
+ "server": {
+ "label": "سرور من",
+ "desc": "یک سرور SSH پیکربندیشده"
+ },
+ "cloud": {
+ "label": "Openship Cloud",
+ "desc": "زیرساخت کلاد مدیریتشده"
+ }
+ },
+ "labelFor": {
+ "yourServer": "سرور شما",
+ "cloud": "Openship Cloud"
+ },
+ "toast": {
+ "pickServer": "ابتدا یک سرور انتخاب کنید",
+ "cleared": "مقصد استقرار پیشفرض پاک شد",
+ "setTo": "پیشفرض روی {label} تنظیم شد",
+ "failed": "بهروزرسانی پیشفرض ناموفق بود"
+ }
+ },
+ "buildPreferences": {
+ "title": "ترجیحات ساخت",
+ "description": "نحوهٔ ساخت و استقرار پروژهها را کنترل کنید",
+ "loading": "در حال بارگذاری ترجیحات…",
+ "intro": "استراتژی ساخت پیشفرض برای استقرارهای جدید را انتخاب کنید. لغو بهازای پروژه در تنظیمات پروژه در دسترس است.",
+ "modes": {
+ "auto": {
+ "label": "خودکار",
+ "desc": "هوشمند بهازای فریمورک"
+ },
+ "server": {
+ "label": "سرور",
+ "desc": "ساخت روی راه دور"
+ },
+ "local": {
+ "label": "محلی",
+ "desc": "ساخت روی ماشین"
+ }
+ },
+ "toast": {
+ "setTo": "حالت ساخت روی {mode} تنظیم شد",
+ "failed": "بهروزرسانی حالت ساخت ناموفق بود"
+ }
+ },
+ "cloneCredentials": {
+ "title": "اعتبارنامههای کلون GitHub",
+ "description": "توکن شخصی برای کلون مخزنهای خصوصی وقتی GitHub App نصب نشده",
+ "loading": "در حال بارگذاری…",
+ "intro": "یک توکن دسترسی شخصی GitHub بچسبانید (classic یا fine-grained). ماژول کلون آن را بهعنوان اعتبارنامهٔ اولویت دوم استفاده میکند — بعد از هر لغو بهازای پروژه، قبل از توکن نصب GitHub App.",
+ "placeholder": "ghp_… یا github_pat_…",
+ "showToken": "نمایش توکن",
+ "hideToken": "پنهان کردن توکن",
+ "saveToken": "ذخیرهٔ توکن",
+ "scopeHintPrefix": "برای مخزنهای خصوصی، به توکن بدهید محدودهٔ",
+ "scopeHintSuffix": "(classic) یا دسترسی خواندن fine-grained به مخزنهایی که میخواهید استقرار دهید.",
+ "tokenSaved": "توکن ذخیره شد",
+ "lastUpdated": "آخرین بهروزرسانی",
+ "justNow": "همین حالا",
+ "replace": "جایگزینی",
+ "clear": "پاک کردن",
+ "useAsDefault": "استفاده بهعنوان توکن کلون پیشفرض",
+ "useAsDefaultDesc": "وقتی روشن باشد، ماژول کلون این توکن را بهعنوان لایهٔ دوم زنجیره استفاده میکند (بعد از لغوهای بهازای پروژه). وقتی خاموش باشد، توکن ذخیره میشود اما استفاده نمیشود — مفید اگر فقط برای استقرارهای خاص فعالش بخواهید.",
+ "forwardGitLabel": "هویت git من را به سرورهای ساخت بفرست",
+ "forwardGitDesc": "وقتی ساخت روی سرور راه دور اجرا شود، ورود GitHub CLI محلی شما از روی تونل SSH فرستاده میشود تا مخزنهای خصوصی مستقیماً روی سرور کلون شوند — بدون نیاز به GitHub App. وقتی خاموش باشد، سرور به ورود git خودش، توکن ذخیرهشده، یا مسیر کلون عمومی/محلی برمیگردد.",
+ "forwardGitFailed": "بهروزرسانی ارسال git ناموفق بود",
+ "toast": {
+ "pasteFirst": "قبل از ذخیره یک توکن بچسبانید",
+ "notLikeToken": "شبیه توکن GitHub نیست. اگر مطمئنید باز هم ذخیره کنید.",
+ "saved": "توکن کلون ذخیره شد",
+ "saveFailed": "ذخیرهٔ توکن ناموفق بود",
+ "cleared": "توکن کلون پاک شد",
+ "clearFailed": "پاک کردن توکن ناموفق بود",
+ "updateDefaultFailed": "بهروزرسانی پیشفرض ناموفق بود"
+ }
+ },
+ "tokens": {
+ "title": "توکنهای دسترسی شخصی",
+ "description": "توکنهای Bearer برای API، CLI و کلاینتهای MCP. هر توکن بهجای شما عمل میکند.",
+ "explainer1": "بهصورت پیشفرض یک توکن",
+ "explainerActsAsYou": "بهجای شما عمل میکند",
+ "explainer2": "— مجوزهای نقش شما را حمل میکند.",
+ "explainerReadOnly": "فقطخواندنی",
+ "explainer3": "همهٔ نوشتنها را مسدود میکند، و",
+ "explainerLimit": "محدود به منابع مشخص",
+ "explainer4": "توکن را دقیقاً به آنچه انتخاب میکنید محدود میکند — حتی پایینتر از نقش خودتان. ایدهآل برای یک توکن باریک MCP.",
+ "copyNow": "توکن تازه را همین حالا کپی کنید",
+ "onlyTime": "این تنها باری است که نمایش داده میشود. آن را جایی امن ذخیره کنید.",
+ "namePlaceholder": "نام توکن (مثلاً MCP client)",
+ "readOnly": "فقطخواندنی",
+ "limitToResources": "محدود به منابع مشخص",
+ "chooseResources": "انتخاب منابع",
+ "resourcesSelectedOne": "{count} منبع انتخاب شد",
+ "resourcesSelectedMany": "{count} منبع انتخاب شد",
+ "noResourcesSelected": "هنوز منبعی انتخاب نشده",
+ "createToken": "ساخت توکن",
+ "newToken": "توکن تازه",
+ "loading": "در حال بارگذاری…",
+ "noActive": "توکن فعالی نیست.",
+ "badgeReadOnly": "فقطخواندنی",
+ "badgeScoped": "محدودشده",
+ "metaLine": " · آخرین استفاده {lastUsed} · انقضا {expires}",
+ "callsOne": " · ۱ فراخوانی",
+ "callsMany": " · {count} فراخوانی",
+ "revoke": "لغو",
+ "expiry": {
+ "none": "بدون انقضا",
+ "days30": "۳۰ روز",
+ "days90": "۹۰ روز",
+ "year1": "۱ سال"
+ },
+ "scopePicker": {
+ "title": "محدودهٔ توکن",
+ "subtitle": "توکن دقیقاً به این منابع محدود است. فقط دسترسیای را میتوانید بدهید که خودتان دارید.",
+ "saveLabel": "انجام شد"
+ },
+ "toast": {
+ "giveName": "به توکن یک نام بدهید",
+ "pickResource": "حداقل یک منبع انتخاب کنید، یا محدودسازی را خاموش کنید",
+ "createFailed": "ساخت توکن ناموفق بود",
+ "revoked": "«{name}» لغو شد",
+ "revokeFailed": "لغو توکن ناموفق بود",
+ "loadFailed": "بارگذاری توکنها ناموفق بود"
+ }
+ },
+ "mcp": {
+ "title": "MCP",
+ "description": "عاملهای هوش مصنوعی را از طریق Model Context Protocol به API مربوط به Openship وصل کنید.",
+ "loading": "در حال بارگذاری…",
+ "connectAnother": "اتصال کلاینت دیگر",
+ "bannerStrong": "نقطهٔ پایانی زیر را در کلاینت MCP خود بچسبانید",
+ "bannerRest": "(Claude، Cursor، …) — پنجرهٔ مرورگری باز میشود که در آن دسترسی را تأیید میکنید و انتخاب میکنید کلاینت به چه چیزی برسد. توکنی برای کپی نیست. Openship یک سرور MCP سازگار با استاندارد OAuth 2.1 است.",
+ "addToClient": "افزودن به کلاینت",
+ "install": "نصب",
+ "endpoint": "نقطهٔ پایانی",
+ "endpointNote": "JSON-RPC از نوع Streamable-HTTP. کلاینتهای دارای OAuth در مرورگر مجوز میگیرند؛ هنگام تأیید، فقطخواندنی و منابعی که کلاینت میتواند به آنها دسترسی داشته باشد را انتخاب میکنید.",
+ "authentication": "احراز هویت",
+ "oauth": "OAuth",
+ "staticToken": "توکن ثابت",
+ "oauthNote": "کلاینت برای مجوز یک مرورگر باز میکند — دسترسی را تأیید میکنید و انتخاب میکنید به چه برسد. چیزی برای کپی نیست.",
+ "tokenNote": "برای کلاینتهای بدون OAuth، یا وقتی میخواهید یک توکن ثابت و محدود در پیکربندی باشد.",
+ "clientConfig": "پیکربندی کلاینت",
+ "createTokenPrefix": "یک توکن بسازید در زبانهٔ",
+ "tokensTab": "توکنها",
+ "createTokenMid": "و جایگزین کنید",
+ "createTokenSuffix": ". یک توکن فقطخواندنی عامل را به خواندن محدود میکند.",
+ "connectedClients": "کلاینتهای متصل",
+ "clientReadOnly": "فقطخواندنی",
+ "clientFullControl": "کنترل کامل",
+ "allResources": "همهٔ منابع",
+ "resourcesOne": "{count} منبع",
+ "resourcesMany": "{count} منبع",
+ "orgPrefix": "{org} · ",
+ "authorized": "مجاز شده {date}",
+ "lastUsedSuffix": " · آخرین استفاده {date}",
+ "callsOne": " · ۱ فراخوانی",
+ "callsMany": " · {count} فراخوانی",
+ "toast": {
+ "disconnected": "کلاینت MCP قطع شد",
+ "disconnectFailed": "قطع اتصال ناموفق بود"
+ },
+ "editAccess": "ویرایش دسترسی",
+ "viewActivity": "فعالیت",
+ "editScopeTitle": "دسترسی {client}",
+ "editScopeSubtitle": "تغییرات در درخواست بعدی عامل اعمال میشود — نیازی به اتصال دوباره نیست.",
+ "editScopeOrg": "در {org}",
+ "wasNResourcesOne": "در حال حاضر ۱ منبع",
+ "wasNResourcesMany": "در حال حاضر {count} منبع",
+ "saveScope": "ذخیرهٔ دسترسی",
+ "scopeSaved": "دسترسی بهروز شد",
+ "scopeSaveFailed": "بهروزرسانی دسترسی ممکن نشد",
+ "loadScopeFailed": "بارگذاری دسترسی این کلاینت ممکن نشد",
+ "scopeGrantRejected": "دیگر نمیتوانید {resource} را اعطا کنید، بنابراین حذف شد. برای ادامه دوباره ذخیره کنید.",
+ "confirmWidenTitle": "این همهٔ محدودیتها را برمیدارد",
+ "confirmWidenBody": "عامل بلافاصله با دسترسی کامل شما عمل میکند. برای تأیید دوباره فشار دهید.",
+ "confirmWidenAction": "برداشتن همهٔ محدودیتها"
+ },
+ "team": {
+ "heading": "تیم",
+ "reachability": {
+ "description": "همتیمیها برای رسیدن به این فضای کاری به یک URL عمومی نیاز دارند.",
+ "notInstalledTitle": "به Openship یک آدرس عمومی بدهید",
+ "notInstalledBody": "Openship هنوز با یک URL عمومی راهاندازی نشده. آن را با یک آدرس عمومی استقرار دهید (مثلاً `openship up --public-url …`)، سپس اینجا یک دامنه اضافه کنید تا همتیمیها برسند.",
+ "noDomainTitle": "یک دامنه به Openship اضافه کنید",
+ "noDomainBody": "یک دامنه به اپ Openship اضافه کنید تا همتیمیها به این فضای کاری برسند — دعوتها بهمحض زنده شدن بهصورت خودکار روشن میشوند.",
+ "addDomain": "افزودن دامنه",
+ "pendingTitle": "راهاندازی دامنه را تمام کنید",
+ "pendingBody": "دامنهٔ Openship شما در انتظار تأیید است (DNS / SSL). دعوتها وقتی زنده شود روشن میشوند.",
+ "viewDomain": "مشاهدهٔ دامنه"
+ },
+ "descPersonal": "افرادی که به فضای کاری شخصی شما دسترسی دارند.",
+ "descTeam": "افرادی که به پروژهها، استقرارها و سرورهای این سازمان دسترسی دارند.",
+ "inviteMember": "دعوت عضو",
+ "createTeamCard": {
+ "title": "ساخت سازمان تیمی",
+ "body": "میتوانید افراد را همینجا مستقیم دعوت کنید — یا یک فضای مشترک جدا بسازید که از پروژههای شخصیتان جدا بماند.",
+ "button": "ساخت تیم"
+ },
+ "activeMembers": "اعضای فعال ({count})",
+ "you": "(شما)",
+ "removeMember": "حذف عضو",
+ "pendingInvitations": "دعوتهای در انتظار ({count})",
+ "invitedAs": "دعوتشده بهعنوان {role} - انقضا {date}",
+ "roles": {
+ "owner": "مالک",
+ "admin": "مدیر",
+ "member": "عضو",
+ "restricted": "محدود"
+ },
+ "createTeamModal": {
+ "title": "ساخت سازمان تیمی",
+ "body": "فضای کاری شخصیتان دستنخورده میماند. سازمان تیمی پروژهها، سرورها و اعضای دعوتشدهٔ خودش را دارد.",
+ "teamName": "نام تیم",
+ "placeholder": "Acme Inc",
+ "createTeam": "ساخت تیم"
+ },
+ "memberPanel": {
+ "subtitle": "اعطاها برای اعضای محدود اعمال میشود. مالک/مدیر/عضو بهصورت پیشفرض دسترسی سراسری سازمان دارند.",
+ "saveLabel": "ذخیرهٔ دسترسی"
+ },
+ "serverAccessWarning": {
+ "title": "دسترسی کامل سرور اعطا شود؟",
+ "body": "{member} کنترل کامل {scope} را بهدست میآورد — هر استقرار و دادهاش، همهٔ اپهای در حال اجرا روی آن، اسرار و متغیرهای محیطی ذخیرهشده، دسترسی ترمینال، و هر یکپارچهسازی متصل (مثلاً توکنهای GitHub و کلیدهای استقرار نگهداریشده برای آن). میتوانند این منابع را مدیریت یا حذف کنند.",
+ "scopeAllServers": "همهٔ سرورهای این سازمان",
+ "scopeThisServer": "این سرور",
+ "scopeCount": "{count} سرور",
+ "confirm": "اعطای دسترسی"
+ },
+ "confirmRemove": "این عضو از سازمان حذف شود؟",
+ "workspace": {
+ "deleteWorkspace": "حذف فضای کاری",
+ "leaveWorkspace": "ترک فضای کاری",
+ "deleteBody": "این فضای کاری و همهٔ محتویاتش را برای همیشه حذف کنید.",
+ "leaveBody": "این فضای کاری را ترک کنید — برای بازگشت به دعوت تازه نیاز دارید.",
+ "deleteConfirm": "این فضای کاری حذف شود؟ پروژهها، اعضا و تنظیماتش برای همه حذف میشوند. این کار برگشتپذیر نیست.",
+ "leaveConfirm": "این فضای کاری را ترک کنید؟ تا دعوت دوباره دسترسی را از دست میدهید.",
+ "manage": {
+ "title": "مدیریت فضای کاری",
+ "sectionBody": "این فضای کاری را تغییر نام دهید، پروژههایش را متوقف کنید، یا آن را حذف کنید.",
+ "nameLabel": "نام فضای کاری",
+ "rename": "تغییر نام",
+ "projects": "پروژهها ({count})",
+ "noProjects": "پروژهای در این فضای کاری نیست.",
+ "running": "در حال اجرا",
+ "stopped": "متوقف",
+ "pauseTitle": "توقف همه",
+ "pauseBody": "هر پروژه را متوقف کنید اما فضای کاری را نگه دارید — هر زمان از سر بگیرید.",
+ "pauseButton": "توقف همهٔ پروژهها",
+ "deleteTitle": "حذف فضای کاری",
+ "deleteBody": "همهٔ {count} پروژه (کانتینرها و مسیرها) را جمع میکند و فضای کاری را حذف میکند. دادهٔ volume قابل بازیابی میماند. برگشتپذیر نیست.",
+ "confirmLabel": "برای تأیید «{name}» را تایپ کنید",
+ "deleting": "در حال حذف…",
+ "deletingProject": "در حال حذف {name}…",
+ "deletingWorkspace": "در حال حذف فضای کاری…",
+ "toastRenamed": "فضای کاری تغییر نام یافت",
+ "toastRenameFailed": "تغییر نام فضای کاری ممکن نشد",
+ "toastPaused": "{count} پروژه متوقف شد",
+ "toastPauseFailed": "بعضی پروژهها متوقف نشدند",
+ "toastProjectFailed": "حذف {name} ممکن نشد"
+ }
+ },
+ "toast": {
+ "teamNameRequired": "نام تیم لازم است",
+ "teamCreated": "تیم «{name}» ساخته شد — در حال جابهجایی…",
+ "createFailed": "ساخت تیم ناموفق بود",
+ "loadFailed": "بارگذاری تیم ناموفق بود",
+ "updateRoleFailed": "بهروزرسانی نقش ناموفق بود",
+ "removeFailed": "حذف ناموفق بود",
+ "cancelFailed": "لغو ناموفق بود",
+ "accessUpdated": "دسترسی بهروز شد",
+ "loadGrantsFailed": "بارگذاری اعطاها ناموفق بود",
+ "deleteFailed": "حذف فضای کاری ممکن نشد",
+ "deleteBillingActive": "قبل از حذف، اشتراک فعال این فضای کاری را لغو کنید.",
+ "deleted": "فضای کاری حذف شد",
+ "leaveFailed": "ترک فضای کاری ممکن نشد",
+ "left": "فضای کاری ترک شد"
+ }
+ },
+ "inviteMember": {
+ "headerTitle": "دعوت یک عضو",
+ "headerDescRestricted": "نقش را در سمت چپ تنظیم کنید، سپس دقیقاً آنچه میتوانند برسند را در سمت راست انتخاب کنید.",
+ "headerDescDefault": "یک لینک برای پیوستن به این سازمان برایشان ایمیل میکنیم.",
+ "email": "ایمیل",
+ "emailPlaceholder": "teammate@example.com",
+ "role": "نقش",
+ "roleMemberTitle": "عضو",
+ "roleMemberDesc": "خواندن + نوشتن روی همهٔ منابع این سازمان.",
+ "roleAdminTitle": "مدیر",
+ "roleAdminDesc": "هر کار عضو، بهعلاوه مدیریت اعضا + صورتحساب.",
+ "roleRestrictedTitle": "محدود",
+ "roleRestrictedDesc": "بدون دسترسی پیشفرض — دقیقاً کدام منابع باز شوند را انتخاب کنید.",
+ "grantsOne": "{count} اعطا",
+ "grantsMany": "{count} اعطا",
+ "sendVia": "ارسال از طریق",
+ "yourMailServer": "سامانهٔ ایمیل شما",
+ "openshipCloud": "Openship Cloud",
+ "mailReady": "آماده",
+ "mailNotSetUp": "راهاندازی نشده",
+ "cloudReady": "متصل",
+ "cloudNotReady": "متصل نیست",
+ "noMailSystem": "سامانهٔ ایمیلی راهاندازی نشده، پس دعوت تحویل داده نمیشود.",
+ "setUpEmail": "راهاندازی ایمیل →",
+ "cloudNotConnected": "Openship Cloud متصل نیست، پس نمیتواند دعوت را بفرستد.",
+ "connectCloud": "اتصال Openship Cloud →",
+ "pickerTitle": "دسترسی اولیه به منابع",
+ "pickerDesc": "اعضای محدود بدون دسترسی شروع میکنند. انتخاب کنید این دعوت پس از پذیرش چه چیزی را باز کند — بعداً میتوانید از ردیف عضو بیشتر اعطا کنید.",
+ "sendInvite": "ارسال دعوت",
+ "toast": {
+ "failedSend": "ارسال دعوت ناموفق بود",
+ "invitationSent": "دعوتنامه به {email} ارسال شد",
+ "personalOrgBlocked": "فضاهای کاری شخصی نمیتوانند عضو دعوت کنند. ابتدا یک سازمان تیمی بسازید، سپس دعوت بفرستید.",
+ "updateMailSourceFailed": "بهروزرسانی منبع ایمیل ناموفق بود"
+ }
+ },
+ "teamWorkspace": {
+ "title": "تیم خود را دعوت کنید",
+ "description": "این فضای کاری را جایی ببرید که همتیمیها بتوانند برسند.",
+ "body": "این فضای کاری فقط روی این رایانه است، پس همتیمیها نمیرسند و دعوتها خاموش است. آن را به یک مکان مشترک ببرید — سرور خودتان، Openship Cloud، یا یک تونل — تا دعوت افراد شروع شود.",
+ "currentMode": "حالت فعلی",
+ "singleUserLocal": "تککاربره (محلی)",
+ "migrate": "مهاجرت",
+ "ownerOnlyTitle": "فقط مالک فضای کاری میتواند مهاجرت کند",
+ "comingSoon": "اتصال مستقیم به یک نمونهٔ راه دور بهزودی میآید."
+ },
+ "cloud": {
+ "checking": "در حال بررسی…",
+ "fallbackName": "Openship Cloud",
+ "connectedAccount": "حساب کلاد متصل",
+ "connected": "متصل",
+ "disconnect": "قطع اتصال",
+ "featuresActive": "استقرار کلاد، SSL مدیریتشده و CDN سراسری برای نمونهٔ شما فعال است.",
+ "connectToCloud": "اتصال به کلاد",
+ "unlockDesktop": "استقرار کلاد، SSL مدیریتشده و CDN سراسری را باز کنید.",
+ "deployLocal": "مستقیماً از نمونهٔ محلی به کلاد استقرار دهید.",
+ "waitingSignIn": "در انتظار ورود…",
+ "connectButton": "اتصال به Openship Cloud",
+ "confirmDisconnect": "از Openship Cloud قطع شود؟ استقرارهای کلاد تا اتصال دوباره کار نمیکنند.",
+ "toast": {
+ "disconnected": "از Openship Cloud قطع شد",
+ "disconnectFailed": "قطع اتصال ناموفق بود"
+ },
+ "pitch": {
+ "title": "افزودن Openship Cloud",
+ "body": "هر پروژه یک دامنهٔ رایگان .{domain} با SSL خودکار میگیرد — بدون DNS، بدون گواهی. سرورهای خودتان دقیقاً مثل الآن کار میکنند.",
+ "perkDomain": "دامنهٔ رایگان .{domain} + SSL خودکار روی هر پروژه",
+ "perkDeploy": "استقرار روی کلاد مدیریتشده، یا ماندن روی سرورهای خودتان",
+ "perkServices": "Postgres، Redis و بیشترِ مدیریتشده، با یک کلیک فراهم میشود",
+ "perkGithub": "GitHub بدون ثبت اپ خودتان متصل میشود",
+ "noLockIn": "اتصال رایگان است. چیزی جابهجا نمیشود و هر زمان میتوانید قطع کنید.",
+ "viewPricing": "مشاهدهٔ قیمتها"
+ }
+ },
+ "instance": {
+ "title": "نمونه",
+ "descDesktop": "اپ دسکتاپ",
+ "descCloud": "کلاد",
+ "descSelfHosted": "میزبانی شخصی",
+ "typeDesktop": "دسکتاپ",
+ "typeCloud": "Openship Cloud",
+ "typeSelfHosted": "میزبانی شخصی",
+ "deployMode": "حالت استقرار: {mode}",
+ "authNone": "بدون احراز هویت",
+ "authCloud": "احراز هویت کلاد",
+ "authLocal": "احراز هویت محلی",
+ "localUser": "کاربر محلی",
+ "change": "تغییر"
+ },
+ "upgradeAuth": {
+ "title": "افزودن رمز عبور",
+ "description": "این نمونهٔ بدون احراز هویت را به ورود واقعی ایمیل + رمز عبور ارتقا دهید. پروژهها و تاریخچه دستنخورده میمانند.",
+ "close": "بستن",
+ "name": "نام",
+ "namePlaceholder": "Jane Doe",
+ "email": "ایمیل",
+ "emailPlaceholder": "you@example.com",
+ "password": "رمز عبور",
+ "passwordPlaceholder": "حداقل ۸ نویسه",
+ "useMailServer": "استفاده از سرور ایمیل شما",
+ "useMailServerDesc": "بازنشانی رمز، تأییدها و دعوتهای تیم را از سرور ایمیل فراهمشده بفرستید بهجای پشتیبان SMTP مبتنی بر محیط.",
+ "createAccount": "ساخت حساب",
+ "toast": {
+ "passwordTooShort": "رمز عبور باید حداقل ۸ نویسه باشد",
+ "accountCreated": "حساب ساخته شد — اکنون وارد شدهاید",
+ "failedUpgrade": "ارتقا ناموفق بود"
+ }
+ },
+ "notifications": {
+ "channels": {
+ "title": "کانالهای تحویل",
+ "description": "اعلانهای شما کجا بروند. کانالهای تأییدشده در دسترساند؛ تأییدنشدهها رد میشوند.",
+ "empty": "هنوز کانالی نیست. یکی اضافه کنید تا اعلان دریافت کنید.",
+ "verified": "تأییدشده",
+ "unverified": "تأییدنشده",
+ "sendTest": "ارسال آزمایش",
+ "testSent": "آزمایش ارسال شد",
+ "deleteChannel": "حذف کانال",
+ "addChannel": "افزودن کانال",
+ "confirmDelete": "این کانال حذف شود؟ اشتراکهایی که از آن استفاده میکنند متوقف میشوند.",
+ "channelRemoved": "کانال حذف شد",
+ "channelAdded": "کانال اضافه شد"
+ },
+ "describe": {
+ "slackWebhook": "وبهوک Slack",
+ "discordWebhook": "وبهوک Discord",
+ "msteamsWebhook": "وبهوک Microsoft Teams",
+ "inApp": "آیکون زنگ داشبورد"
+ },
+ "kinds": {
+ "email": "ایمیل",
+ "webhook": "وبهوک",
+ "slack": "Slack",
+ "discord": "Discord",
+ "msteams": "Microsoft Teams",
+ "telegram": "Telegram",
+ "in_app": "درونبرنامه"
+ },
+ "form": {
+ "labelPlaceholder": "برچسب (مثلاً ایمیل کشیک)",
+ "emailPlaceholder": "you@example.com",
+ "webhookPlaceholder": "https://example.com/openship-hook",
+ "slackPlaceholder": "https://hooks.slack.com/services/...",
+ "discordPlaceholder": "https://discord.com/api/webhooks/...",
+ "msteamsPlaceholder": "https://prod-00.westus.logic.azure.com/workflows/...",
+ "telegramTokenPlaceholder": "توکن ربات از @BotFather",
+ "telegramChatPlaceholder": "Chat ID (مثلاً -1001234567890)",
+ "telegramHint": "با @BotFather یک ربات بسازید، سپس به ربات پیام دهید (یا به گروه اضافه کنید) تا اجازهٔ پاسخ داشته باشد.",
+ "adding": "در حال افزودن…",
+ "addChannel": "افزودن کانال",
+ "labelRequired": "برچسب لازم است",
+ "noEmailTransport": "سرور ایمیلی روی این نمونه راهاندازی نشده، پس اعلانهای ایمیلی تحویل داده نمیشوند.",
+ "setUpSmtp": "راهاندازی SMTP →",
+ "otherAddress": "آدرس دیگر…"
+ },
+ "subscriptions": {
+ "title": "از چه چیزی مطلع شوید",
+ "description": "انتخاب کنید کدام رویدادها به کدام کانال بروند. هر سلول یک اشتراک است.",
+ "empty": "ابتدا یک کانال اضافه کنید تا در رویدادها مشترک شوید.",
+ "allTab": "همه",
+ "eventHeader": "رویداد",
+ "cellAria": "{category} از طریق {channel}",
+ "notifyMe": "به من اطلاع بده"
+ },
+ "orgDefaults": {
+ "title": "پیشفرضهای سازمان",
+ "description": "پیشفرضهایی که وقتی عضو تازهای به این سازمان میپیوندد اعمال میشود. اعضا میتوانند بهازای کانال لغو کنند.",
+ "notifyAria": "اعلان برای {category}",
+ "nChannels": "{n} کانال"
+ }
+ },
+ "audit": {
+ "heading": "گزارش حسابرسی",
+ "subtitle": "هر اتفاقی در این سازمان، چه کسی انجام داد، و چه زمانی.",
+ "searchPlaceholder": "جستجوی اپها، سرورها، دامنهها، افراد…",
+ "noMatch": "چیزی با این فیلترها جور نیست.",
+ "noEvents": "هنوز چیزی ثبت نشده.",
+ "clearFilters": "پاک کردن فیلترها",
+ "showing": "نمایش {from}-{to} از {total}",
+ "actor": "عامل",
+ "name": "نام",
+ "email": "ایمیل",
+ "userId": "شناسهٔ کاربر",
+ "ip": "IP",
+ "userAgent": "عامل کاربر",
+ "resource": "منبع",
+ "type": "نوع",
+ "id": "ID",
+ "changes": "تغییرات",
+ "before": "قبل",
+ "after": "بعد",
+ "removed": "حذفشده",
+ "system": "سیستم",
+ "actorPrefix": "عامل {id}",
+ "recording": {
+ "title": "ثبت فعالیت",
+ "description": "از هر تغییر در این سازمان سابقه نگه دارید. بهصورت پیشفرض روشن است.",
+ "paused": "ثبت خاموش است. چیز تازهای اضافه نمیشود — ورودیهای زیر همانهایی هستند که قبل از خاموش شدن ثبت شدهاند.",
+ "retention": "نگهداشتن ورودیها برای",
+ "retentionOption": "{days} روز",
+ "retentionHint": "ورودیهای قدیمیتر بهصورت خودکار حذف میشوند.",
+ "adminOnly": "فقط مالکان و مدیران میتوانند این را تغییر دهند.",
+ "saveFailed": "ذخیرهٔ تنظیمات فعالیت ممکن نشد."
+ },
+ "filters": {
+ "anyone": "هر کسی",
+ "anyAgent": "هر عامل",
+ "agentCalls": "{count} رویداد",
+ "period": "بازه",
+ "allTime": "همهٔ زمانها",
+ "today": "امروز",
+ "last7": "۷ روز گذشته",
+ "last30": "۳۰ روز گذشته",
+ "last90": "۹۰ روز گذشته"
+ },
+ "tabs": {
+ "all": "همه"
+ },
+ "sources": {
+ "all": "همه چیز",
+ "dashboard": "داشبورد",
+ "mcp": "دستیار هوش مصنوعی",
+ "cli": "CLI",
+ "api": "API",
+ "webhook": "وبهوک",
+ "system": "سیستم",
+ "unknown": "نامشخص"
+ },
+ "day": {
+ "today": "امروز",
+ "yesterday": "دیروز"
+ },
+ "details": {
+ "who": "چه کسی",
+ "when": "چه زمانی",
+ "cameFrom": "از کجا آمد",
+ "viaAgent": "عامل",
+ "technical": "جزئیات فنی",
+ "eventType": "نوع رویداد"
+ }
+ },
+ "updates": {
+ "title": "بهروزرسانیها",
+ "description": "Openship چگونه نسخههای تازه را بررسی و اعمال میکند",
+ "checking": "در حال بررسی بهروزرسانیها…",
+ "available": "Openship {version} در دسترس است",
+ "upToDate": "روی آخرین نسخه هستید",
+ "current": "فعلی: {version}",
+ "rerunToUpdate": " · نصب را دوباره اجرا کنید تا بهروز شود",
+ "updateNow": "همین حالا بهروزرسانی",
+ "checkNow": "بررسی الآن",
+ "autoUpdateLabel": "بهروزرسانی خودکار",
+ "autoUpdateDesc": "نسخههای تازه را بهصورت خودکار دانلود و نصب کنید. بهصورت پیشفرض خاموش است — کنترل زمان تغییر اپ با شما میماند.",
+ "notificationsLabel": "اعلانهای بهروزرسانی و امنیت",
+ "notificationsDesc": "وقتی نسخهٔ تازه یا هشدار امنیتی نسخهٔ شما را تحت تأثیر قرار دهد اعلان نشان بده. هشدارهای امنیتی بحرانی همیشه یکبار نشان داده میشوند، حتی وقتی این خاموش باشد.",
+ "viewChangelog": "مشاهدهٔ تغییرات و نسخهها",
+ "securityTitle": "بررسی بهروزرسانی چگونه کار میکند",
+ "securityDescription": "شفافیت کامل دربارهٔ آنچه با نصب شما صحبت میکند",
+ "security1": "Openship بهروزرسانیها و هشدارهای امنیتی را بررسی میکند",
+ "securityOnlyGithub": "فقط از نسخههای عمومی GitHub",
+ "security2": "مخزن متنباز (",
+ "security3": "). فقط همیشه",
+ "securityPulls": "دریافت میکند",
+ "security4": "این داده را — هیچ سرویس شخص ثالث و هیچ سرور تحت ادارهٔ Openship نمیتواند بهروزرسانی یا سیگنال به نصب شما بفرستد. هشدارها به هر نسخه سنجاق شدهاند، پس تغییرات منتشرنشده هرگز به شما نمیرسند. هشدارهای امنیتی بحرانی همیشه یکبار نشان داده میشوند؛ بقیه از کلید بالا پیروی میکنند."
+ },
+ "infrastructure": {
+ "title": "اجزای پلتفرم",
+ "description": "کانتینرهای لبه و ایمیل روی سرورهای شما چگونه بهروز نگه داشته میشوند",
+ "autoUpdateLabel": "بهروزرسانی خودکار زیرساخت سرور",
+ "autoUpdateDesc": "وقتی این صفحهٔ کنترل بهروز شود، کانتینرهای لبه و ایمیل روی سرورهای متصل را بهصورت خودکار بهروز کنید. بهصورت پیشفرض خاموش است — در غیر این صورت بهروزرسانیها برای اعمال یککلیکی روی هر سرور علامت میخورند.",
+ "autoScanLabel": "پویش خودکار زیرساخت سرور",
+ "autoScanDesc": "وقتی صفحهٔ سرورها یا صفحهٔ اصلی را باز میکنید بیصدا سرورها را دوباره بررسی کنید تا لبهٔ خاموش یا گمشده بدون پویش دستی علامت بخورد. بهصورت پیشفرض روشن است — فقط تشخیص، هیچ چیز را تغییر نمیدهد.",
+ "attentionOne": "۱ جزء نیاز به توجه دارد",
+ "attentionMany": "{n} جزء نیاز به توجه دارند",
+ "updatesOne": "۱ بهروزرسانی موجود است",
+ "updatesMany": "{n} بهروزرسانی موجود است",
+ "allHealthy": "همهٔ اجزای پلتفرم بهروز هستند",
+ "manageLink": "مدیریت در سرورها",
+ "hostControlLabel": "استقرار روی این ماشین",
+ "hostControlDesc": "به Openship اجازه دهید اپها را روی سروری که روی آن اجرا میشود، در کنار سرورهای متصل، استقرار دهد. وقتی با --no-host-control نصب شود خاموش است (فقط صفحهٔ کنترل) — روشن کنید تا این ماشین بهعنوان مقصد استقرار اضافه شود.",
+ "hostControlEnvNote": "این ماشین هنگام نصب فقط صفحهٔ کنترل تنظیم شد (--no-host-control). روشن کردن این آن را لغو میکند تا Openship بتواند اینجا استقرار دهد.",
+ "hostControlError": "تغییر کنترل میزبان ممکن نشد — فقط مالک فضای کاری این ماشین میتواند."
+ },
+ "dataTransfer": {
+ "export": {
+ "title": "خروجی داده",
+ "description": "کل پایگاه دادهٔ نمونه را بهصورت فایل دانلود کنید.",
+ "intro": "یک تصویر قابلحمل از همه چیز میسازد — پروژهها، سرورها، استقرارها، تنظیمات. یک عبارت عبور بگذارید تا اسرار شما (متغیرهای محیطی، توکنها، اعتبارنامههای SSH) هم بیاید؛ آنها زیر آن دوباره رمزنگاری میشوند، پس فایل برای جابهجایی امن است. خالی بگذارید تا بدون اسرار خروجی بگیرید.",
+ "passphraseLabel": "عبارت عبور (اختیاری)",
+ "passphrasePlaceholder": "از اسرار شما محافظت میکند",
+ "confirmLabel": "تأیید عبارت عبور",
+ "confirmPlaceholder": "آن را تکرار کنید",
+ "mismatch": "عبارتهای عبور یکسان نیستند.",
+ "exporting": "در حال خروجی…",
+ "exportDownload": "خروجی و دانلود",
+ "toastWithSecrets": "خروجی دانلود شد. عبارت عبور را امن نگه دارید — برای ورود لازم است.",
+ "toastNoSecrets": "خروجی دانلود شد. اسرار شامل نشدند (عبارت عبوری تنظیم نشده).",
+ "toastFailed": "خروجی ناموفق بود."
+ },
+ "import": {
+ "title": "ورود داده",
+ "description": "یک فایل خروجیگرفته را روی این نمونه بازیابی کنید.",
+ "intro": "فایلی را که از نصب دیگر Openship خروجی گرفته شده بارگذاری کنید. عبارت عبور زمان خروجی را وارد کنید تا اسرار هم بازیابی شوند.",
+ "importFromFile": "ورود از فایل…",
+ "modalTitle": "ورود دادهٔ نمونه",
+ "modalSubtitle": "یک تصویر خروجیگرفته را بازیابی کنید.",
+ "exportFile": "فایل خروجی",
+ "fileContainsSecrets": " · شامل اسرار است (عبارت عبور لازم است)",
+ "fileNoSecrets": " · اسراری شامل نشده",
+ "passphrase": "عبارت عبور",
+ "passphrasePlaceholder": "عبارت عبور تنظیمشده هنگام خروجی",
+ "mode": "حالت",
+ "modeReplaceTitle": "جایگزینی همه چیز",
+ "modeReplaceDesc": "این نمونه را پاک کنید و تصویر را دقیقاً بازیابی کنید. توصیهشده برای مهاجرت به ماشین تازه.",
+ "modeMergeTitle": "ادغام در موجود",
+ "modeMergeDesc": "دادهٔ فعلی را نگه دارید و دادهٔ واردشده را اضافه کنید. حساب و تنظیمات خودتان حفظ میشوند.",
+ "wipeWarn": "این همهٔ دادهٔ فعلی این نمونه را پاک میکند و شما را خارج میکند.",
+ "importing": "در حال ورود…",
+ "import": "ورود",
+ "confirmWipe": "همهٔ دادهٔ این نمونه با تصویر واردشده جایگزین شود؟ این کار برگشتپذیر نیست و از حساب خارج میشوید.",
+ "notExport": "شبیه فایل خروجی Openship نیست.",
+ "cantRead": "خواندن آن فایل ممکن نشد — آیا خروجی معتبر است؟",
+ "importFailed": "ورود ناموفق بود.",
+ "rowsRestored": "{count} ردیف بازیابی شد",
+ "secretsRestored": "{count} راز بازیابی شد",
+ "secretsNotRestored": "اسرار بازیابی نشدند (عبارت عبور نیست/نادرست) — اعتبارنامهها را دوباره وارد کنید"
+ }
+ },
+ "migrate": {
+ "chooseTitle": "رفتن به حالت چندکاربره",
+ "serverTitle": "مهاجرت به سرور شما",
+ "cloudTitle": "مهاجرت به Openship Cloud",
+ "tunnelTitle": "نمایش از طریق تونل",
+ "completeTitle": "مهاجرت کامل شد",
+ "back": "بازگشت",
+ "chooseIntro": "الان این نمونه تککاربره است. انتخاب کنید همتیمیها چگونه به آن برسند.",
+ "cards": {
+ "serverTitle": "مهاجرت به سرور شما",
+ "serverBody": "openship را با SSH روی VPS خودتان استقرار دهید. داده به آنجا میرود. بهترین برای تیمهایی که از قبل زیرساخت دارند.",
+ "serverMeta": "بدون وابستگی خارجی",
+ "cloudTitle": "مهاجرت به Openship Cloud",
+ "cloudBody": "دادهٔ خود را به api.openship.io بفرستید. ما همه چیز را میزبانی میکنیم. بهترین برای تیمهایی که صفر زیرساخت میخواهند.",
+ "cloudMetaConnected": "حساب کلاد متصل است",
+ "metaRequiresCloud": "نیاز به حساب کلاد دارد",
+ "cloudWarn": "ابتدا به Openship Cloud وصل شوید.",
+ "tunnelTitle": "محلی بماند، از طریق تونل نمایش داده شود",
+ "tunnelBody": "دادهٔ شما روی این ماشین میماند. یک تونل لبهٔ Oblien فراهم میکنیم تا همتیمیها از اینترنت عمومی به شما برسند — ماشین شما باید آنلاین باشد.",
+ "tunnelMetaConnected": "تونل از طریق Oblien",
+ "tunnelWarn": "تونلها از طریق حساب کلاد شما فراهم میشوند."
+ },
+ "server": {
+ "targetServer": "سرور مقصد",
+ "loadingServers": "در حال بارگذاری سرورها...",
+ "noServersPrefix": "سروری پیکربندی نشده. یکی اضافه کنید در",
+ "pickServer": "انتخاب سرور",
+ "domain": "دامنه",
+ "freeSubdomain": "زیردامنهٔ رایگان",
+ "freeSubdomainHint": "name.opsh.io",
+ "customDomain": "دامنهٔ سفارشی",
+ "customDomainHint": "DNS به سرور اشاره کند",
+ "freeSlugPlaceholder": "myteam",
+ "customHostPlaceholder": "team.acme.com",
+ "preflight": "پیشپرواز",
+ "sshReachable": "SSH در دسترس",
+ "releaseAvailable": "نسخهٔ Openship موجود است",
+ "domainReady": "دامنه آماده است",
+ "runPreflight": "اجرای پیشپرواز",
+ "recheck": "بررسی دوباره",
+ "deploying": "در حال استقرار...",
+ "startMigration": "شروع مهاجرت",
+ "successDetail": "openship اکنون روی سرور شما اجرا میشود. شناسهٔ پروژه {projectId}.",
+ "toastLoadFailed": "بارگذاری سرورها ناموفق بود",
+ "toastPreflightFailed": "پیشپرواز ناموفق بود",
+ "toastMigrationFailed": "مهاجرت ناموفق بود"
+ },
+ "cloud": {
+ "connectFirstTitle": "ابتدا حساب کلاد خود را وصل کنید",
+ "connectFirstBody": "تنظیمات → کلاد را باز کنید و در api.openship.io وارد شوید. سپس برای مهاجرت به اینجا برگردید.",
+ "whatHappens": "وقتی مهاجرت را بزنید چه میشود",
+ "bullet1": "پایگاه دادهٔ محلی dump میشود (فیلدهای رمزنگاریشده حذف میشوند — اعتبارنامهها این ماشین را ترک نمیکنند).",
+ "bullet2": "dump زیر سازمان فعال شما به api.openship.io آپلود میشود.",
+ "bullet3": "نمونهٔ محلی به راهاندازیکنندهای تبدیل میشود که به app.openship.io اشاره میکند.",
+ "bullet4": "هر زمان میتوانید برگردید — دادهٔ محلی روی دیسک میماند.",
+ "proceedNonEmpty": "حتی اگر سازمان کلاد از قبل پروژه داشته باشد ادامه بده (تعارضها را خودم حل میکنم)",
+ "pushingData": "در حال ارسال داده...",
+ "startMigration": "شروع مهاجرت",
+ "successDetail": "{projects} پروژه، {deployments} استقرار، {services} سرویس وارد شد.",
+ "toastConflict": "سازمان کلاد شما از قبل {count} پروژه دارد. جعبه را بزنید تا ادامه دهید و دادهٔ این نمونه در کنار آنچه هست بیاید.",
+ "toastFailed": "مهاجرت کلاد ناموفق بود"
+ },
+ "tunnel": {
+ "connectFirstTitle": "ابتدا حساب کلاد خود را وصل کنید",
+ "connectFirstBody": "تونلها از طریق Openship Cloud فراهم میشوند. تنظیمات → کلاد را باز کنید و وارد شوید، سپس به اینجا برگردید.",
+ "headsUp": "توجه",
+ "bullet1": "دادهٔ شما روی این ماشین میماند. چیزی جابهجا نمیشود.",
+ "bullet2": "همتیمیها فقط وقتی این ماشین آنلاین باشد میرسند.",
+ "bullet3": "عامل تونل داخل فرایند API اجرا میشود — سرویس اضافی برای نگهداری نیست.",
+ "tunnelSlug": "اسلاگ تونل",
+ "slugPlaceholder": "myteam",
+ "slugSuffix": ".preview.oblien.com",
+ "slugHint": "۳–۳۲ نویسه، حروف کوچک، رقم، خط تیره. باید با حرف یا رقم شروع و تمام شود.",
+ "provisioning": "در حال فراهمسازی...",
+ "provisionTunnel": "فراهمسازی تونل",
+ "successDetail": "تونل «{slug}» داشبورد محلی شما را منتشر میکند.",
+ "toastSlugTaken": "اسلاگ «{slug}» گرفته شده. دیگری انتخاب کنید.",
+ "toastNeedsCloud": "تونلها به حساب کلاد متصل نیاز دارند. ابتدا تنظیمات → کلاد را باز کنید.",
+ "toastFailed": "فراهمسازی تونل ناموفق بود"
+ },
+ "result": {
+ "complete": "مهاجرت کامل شد",
+ "newLocation": "مکان تازه",
+ "open": "باز کردن",
+ "done": "انجام شد"
+ }
+ },
+ "switchBack": {
+ "nothingTitle": "چیزی برای بازگشت نیست",
+ "nothingBody": "این نمونه از قبل در حالت تککاربره است.",
+ "openDashboard": "باز کردن داشبورد",
+ "resultTitle": "بازگشت به تککاربره",
+ "resultRestored": "{rows} ردیف از راه دور بازیابی شد ({mode}).",
+ "resultLocalTunnel": "حالت محلی بازیابی شد. تونل جمع شد — دادهٔ شما هرگز جابهجا نشده بود.",
+ "resultLocalGrace": "حالت محلی بازیابی شد. دادهٔ راه دور برای مهلت ۳۰ روزه دستنخورده ماند.",
+ "relinkTitle": "اتصال دوبارهٔ اعتبارنامهها",
+ "relinkBody": "ستونهای رمزنگاریشده هنگام جابهجایی بین میزبانها حذف میشوند. قبل از نیاز دوباره اینها را وصل کنید:",
+ "relinkRowOne": "• {table}.{column} ({rows} ردیف)",
+ "relinkRowMany": "• {table}.{column} ({rows} ردیف)",
+ "heading": "بازگشت به تککاربره",
+ "intro": "این نمونه را از {mode} بیرون بکشید و روی این ماشین به حالت انفرادی برگردانید.",
+ "modeServer": "سرور شما",
+ "modeCloud": "Openship Cloud",
+ "modeTunnel": "تونل",
+ "current": "فعلی",
+ "noUrl": "(URL ثبت نشده)",
+ "teammatesLoseAccess": "همتیمیها دسترسی را از دست میدهند",
+ "warnTunnel": "تونل جمع میشود. هر کس از URL عمومی استفاده کند اتصال رد میشود.",
+ "warnRemote": "راه دور دادهٔ شما را ۳۰ روز بهعنوان مهلت نگه میدارد اگر نظرتان عوض شود.",
+ "dontPull": "دادهٔ راه دور را نکش — هر چه الآن روی این ماشین است را نگه دار.",
+ "dontPullDesc": "وقتی راه دور در دسترس نیست یا عمداً میخواهید تغییرات راه دور را دور بریزید از این استفاده کنید.",
+ "back": "بازگشت",
+ "tearingDown": "در حال جمع کردن...",
+ "pullingData": "در حال کشیدن داده...",
+ "switchBack": "بازگشت",
+ "toastLoadStateFailed": "بارگذاری وضعیت ناموفق بود",
+ "toastRemoteUnreachable": "راه دور در دسترس نیست. «دادهٔ راه دور را نکش» را بزنید تا به هر حال برگردید.",
+ "toastSwitchBackFailed": "بازگشت ناموفق بود"
+ },
+ "routePreferences": {
+ "title": "مسیریابی",
+ "description": "لبه چگونه به بالادست اپهای شما میرسد",
+ "loading": "در حال بارگذاری ترجیحات…",
+ "intro": "آدرسدهی بالادست پیشفرض برای پروژههای میزبانی شخصی تازه را انتخاب کنید. لغو بهازای پروژه در تنظیمات پروژه است.",
+ "modes": {
+ "auto": {
+ "label": "خودکار",
+ "desc": "توصیهشده (پورت loopback)"
+ },
+ "loopbackPort": {
+ "label": "پورت loopback",
+ "desc": "پورت خصوصی میزبان 127.0.0.1"
+ },
+ "containerIp": {
+ "label": "IP کانتینر",
+ "desc": "پیشرفته — IP پل"
+ }
+ },
+ "toast": {
+ "setTo": "استراتژی مسیر روی {mode} تنظیم شد",
+ "failed": "بهروزرسانی استراتژی مسیر ناموفق بود"
+ }
+ },
+ "edgeOrphans": {
+ "title": "مسیرهای لبهٔ بدون ردگیری",
+ "description": "نامهای میزبانی که این ماشین هنوز سرو میکند و Openship سابقهای از آنها ندارد",
+ "scan": "پویش",
+ "scanning": "در حال پویش…",
+ "allClear": "هر نام میزبانی که لبه سرو میکند حسابشده است.",
+ "comparedTo": "مقایسه با {count} نام میزبان ردگیریشده.",
+ "unavailable": "بررسی لبه ممکن نشد",
+ "explain": "حذف پروژه با «فقط از Openship حذف شود» عمداً مسیرش را در حال اجرا میگذارد. مسیر اپ باقیمانده پس از رفتن کانتینر 502 برمیگرداند، اما مسیر STATIC باقیمانده فایلهای قدیمی را با 200 سرو میکند — حتی روی نام میزبانی که بعداً به پروژهٔ دیگری داده شده.",
+ "kindStatic": "فایلهای ایستا",
+ "kindProxy": "مسیر اپ",
+ "staticWarning": "هنوز فایل سرو میکند. امروز به درخواستها پاسخ میدهد.",
+ "proxyWarning": "به بالادستی اشاره میکند که احتمالاً رفته است.",
+ "servingFrom": "سرو از",
+ "proxyingTo": "پروکسی به",
+ "alsoAnswers": "همچنین پاسخ میدهد برای",
+ "stopServing": "توقف سرو",
+ "stopping": "در حال توقف…",
+ "stopped": "{hostname} دیگر سرو نمیشود.",
+ "stopFailed": "توقف سرو {hostname} ممکن نشد",
+ "confirmTitle": "سرو {hostname} متوقف شود؟",
+ "confirmBody": "این پیکربندی لبه را فقط برای این نام میزبان حذف میکند. فایلهای ساختهشده روی دیسک میمانند و هیچ چیز دیگری روی سرور دست نمیخورد. اگر بعداً پروژهای روی این نام میزبان استقرار شود مسیر خودش را میسازد.",
+ "confirmCta": "توقف سرو",
+ "cancel": "لغو"
+ },
+ "credentials": {
+ "title": "اعتبارنامهها",
+ "description": "توکنها و ورودهایی که Openship از طرف شما استفاده میکند — کشیدن رجیستری خصوصی، نوشتن رکورد DNS. رمزنگاریشده ذخیره میشوند؛ پس از ذخیره دیگر نمایش داده نمیشوند.",
+ "noProviders": "ارائهدهندهٔ اعتبارنامهای روی این نمونه موجود نیست.",
+ "noneForProvider": "هنوز چیزی اضافه نشده.",
+ "add": "افزودن",
+ "edit": "ویرایش",
+ "save": "ذخیرهٔ تغییرات",
+ "connect": "افزودن اعتبارنامه",
+ "delete": "حذف",
+ "verify": "بررسی دوباره با ارائهدهنده",
+ "badgeActive": "در حال کار",
+ "badgeInvalid": "نیاز به توجه",
+ "fieldName": "برچسب",
+ "namePlaceholder": "مثلاً GHCR فقطخواندنی",
+ "secretKeptPlaceholder": "•••••••• (ذخیرهشده — برای نگه داشتن خالی بگذارید)",
+ "selectPlaceholder": "انتخاب…",
+ "verifyNote": "Openship اعتبارنامه را قبل از ذخیره با ارائهدهنده بررسی میکند.",
+ "confirmDelete": "«{name}» حذف شود؟ هر چیزی که به آن وابسته است از کار میافتد.",
+ "toast": {
+ "loadFailed": "بارگذاری اعتبارنامهها ممکن نشد",
+ "created": "اعتبارنامه اضافه شد",
+ "updated": "اعتبارنامه بهروز شد",
+ "deleted": "اعتبارنامه حذف شد",
+ "saveFailed": "ذخیرهٔ اعتبارنامه ممکن نشد",
+ "deleteFailed": "حذف اعتبارنامه ممکن نشد",
+ "verifyOk": "ارائهدهنده این اعتبارنامه را پذیرفت",
+ "verifyFailed": "ارائهدهنده این اعتبارنامه را رد کرد"
+ }
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/fa/widgets.json b/apps/dashboard/src/i18n/locales/fa/widgets.json
new file mode 100644
index 000000000..15cd11942
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/fa/widgets.json
@@ -0,0 +1,399 @@
+{
+ "shared": {
+ "dnsRecords": {
+ "priority": "اولویت {n}",
+ "recommended": "پیشنهادی",
+ "name": "نام",
+ "value": "مقدار",
+ "copy": "کپی"
+ },
+ "errorState": {
+ "commonCauses": "علل رایج",
+ "needHelp": "کمک میخواهید؟",
+ "needHelpDesc": "مستندات را بررسی کنید یا اگر مشکل ماند با ما تماس بگیرید.",
+ "documentation": "مستندات",
+ "repoNotFound": {
+ "title": "مخزن پیدا نشد",
+ "subtitle": "مخزنی را که دنبالش هستید پیدا نکردیم.",
+ "hints": [
+ "مخزن خصوصی است و دسترسی ندادهاید",
+ "نام مخزن یا مالک اشتباه نوشته شده",
+ "مخزن حذف یا جابهجا شده",
+ "اتصال GitHub شما منقضی شده"
+ ],
+ "backToLibrary": "بازگشت به کتابخانه",
+ "importRepository": "ورود مخزن"
+ },
+ "projectNotFound": {
+ "title": "پروژه پیدا نشد",
+ "subtitle": "پروژهای که دنبالش هستید وجود ندارد یا حذف شده.",
+ "hints": ["پروژه حذف شده", "به این پروژه دسترسی ندارید", "URL پروژه نادرست است"],
+ "backToDashboard": "بازگشت به داشبورد",
+ "createNewProject": "ایجاد پروژهٔ جدید"
+ },
+ "loadFailed": {
+ "title": "بارگذاری این پروژه ممکن نشد",
+ "subtitle": "در رسیدن به این پروژه مشکلی پیش آمد. اتصال را بررسی کنید و دوباره تلاش کنید."
+ },
+ "accessDenied": {
+ "title": "دسترسی رد شد",
+ "subtitle": "مجوز دسترسی به این منبع را ندارید.",
+ "hints": ["مالک پروژه نیستید", "حسابتان مجوزهای لازم را ندارد", "عضویت تیمتان عوض شده"],
+ "backToDashboard": "بازگشت به داشبورد"
+ }
+ },
+ "machineSettings": {
+ "title": "تنظیمات ماشین",
+ "configureFor": "پیکربندی منابع محاسبه برای",
+ "docs": "مستندات",
+ "cpu": "CPU",
+ "memory": "حافظه",
+ "storage": "فضای ذخیرهسازی",
+ "base": "پایه",
+ "current": "فعلی",
+ "creditsPerMonth": "اعتبار/ماه",
+ "fromCurrent": "نسبت به فعلی",
+ "viewPricing": "مشاهدهٔ قیمتگذاری",
+ "cancel": "لغو",
+ "saving": "در حال ذخیره...",
+ "saveChanges": "ذخیرهٔ تغییرات",
+ "toastUpdated": "تنظیمات ماشین بهروز شد",
+ "toastFailed": "بهروزرسانی تنظیمات ناموفق بود"
+ },
+ "serverSelector": {
+ "serverLabel": "سرور",
+ "loadingServers": "در حال بارگذاری سرورها…",
+ "noServerConnected": "سروری متصل نیست",
+ "connectServerFirst": "الان یکی اضافه کنید بدون ترک این صفحه.",
+ "addServer": "افزودن سرور",
+ "selectServer": "انتخاب سرور…",
+ "addNewServer": "افزودن سرور جدید…"
+ }
+ },
+ "backup": {
+ "runCard": {
+ "streamError": "استریم پیشرفت اجرا ممکن نشد: {message}",
+ "loading": "در حال بارگذاری…",
+ "live": "زنده",
+ "started": "شروع شده",
+ "finished": "تمام شده",
+ "elapsed": "گذشته",
+ "bytes": "بایت",
+ "runId": "شناسهٔ اجرا",
+ "status": {
+ "queued": "در صف",
+ "preparing": "در حال آمادهسازی",
+ "snapshotting": "در حال اسنپشات",
+ "uploading": "در حال بارگذاری",
+ "verifying": "در حال تأیید",
+ "succeeded": "موفق",
+ "failed": "ناموفق",
+ "cancelled": "لغو شده",
+ "serverError": "خطای سرور"
+ },
+ "phase": {
+ "queued": "در انتظار ورکر…",
+ "preparing": "در حال حل مقصد + سرویس…",
+ "snapshotting": "در حال تولید آرتیفکت (pre-hook در حال اجرا)…",
+ "uploading": "در حال استریم به مقصد…",
+ "verifying": "در حال نوشتن مانیفست…"
+ }
+ },
+ "policyEditor": {
+ "presetHourly": "ساعتی",
+ "presetDaily": "روزانه (۰۳:۱۷)",
+ "presetWeekly": "هفتگی (یکشنبه ۰۳:۱۷)",
+ "presetMonthly": "ماهانه (اول ماه ۰۳:۱۷)",
+ "presetManual": "فقط دستی",
+ "selectDestinationAlert": "یک مقصد انتخاب کنید",
+ "failedSave": "ذخیرهٔ سیاست ناموفق بود",
+ "rotateConfirm": "توکن وبهوک چرخانده شود؟ URL فعلی از کار میافتد.",
+ "failedRotate": "چرخاندن توکن ناموفق بود",
+ "editTitle": "ویرایش سیاست پشتیبان",
+ "createTitle": "ایجاد سیاست پشتیبان",
+ "serviceLabel": "سرویس: {name}",
+ "projectLevel": "سیاست سطح پروژه",
+ "destination": "مقصد",
+ "selectOption": "— انتخاب —",
+ "schedule": "زمانبندی",
+ "cronPlaceholder": "cron سفارشی (۵ فیلد)، یا برای فقطدستی خالی بگذارید",
+ "retainCount": "تعداد نگهداری",
+ "retainCountHint": "N اجرای موفق اخیر را نگه دار",
+ "retainDays": "روزهای نگهداری",
+ "retainDaysHint": "اجراهای قدیمیتر از N روز را حذف کن",
+ "preDeployTrigger": "تریگر پیش از استقرار",
+ "preDeployLabel": "قبل از فرود هر استقرار جدید بهطور خودکار پشتیبان بگیر",
+ "preDeployHint": "روی هر ساخت موفق، درست قبل از زنده شدن کد جدید، شلیک میشود.",
+ "webhookTrigger": "تریگر وبهوک",
+ "webhookLabel": "فعالسازی URL وبهوک ورودی",
+ "webhookHint": "توکنی برای هر سیاست میسازد. از هر تریگر خارجی (GitHub Actions، پایش، زمانبند خودتان) به URL با POST بزنید.",
+ "copyUrl": "کپی URL",
+ "rotateToken": "چرخاندن توکن",
+ "preHook": "pre-hook (قبل از اسنپشات اجرا میشود)",
+ "preHookHint": "رایج برای پایگاهداده: pg_dump > /tmp/dump.sql. شکست اجرا را لغو میکند.",
+ "postHook": "post-hook (پس از بارگذاری اجرا میشود)",
+ "postHookHint": "پاکسازی؛ شکستها لاگ میشوند اما اجرا را ناموفق نمیکنند.",
+ "policyEnabled": "سیاست فعال است",
+ "cancel": "لغو",
+ "saving": "در حال ذخیره…",
+ "saveChanges": "ذخیرهٔ تغییرات",
+ "createPolicy": "ایجاد سیاست",
+ "methodLabel": "روش پشتیبان",
+ "methodAuto": "خودکار",
+ "methodAutoDesc": "هوشمند بهازای هر سرویس",
+ "methodVolume": "والیوم",
+ "methodVolumeDesc": "اسنپشات والیومها",
+ "methodCustom": "سفارشی",
+ "methodCustomDesc": "دستور خودتان",
+ "detected": "{label} شناسایی شد — خودکار با {method} پشتیبان میگیرد.",
+ "autoNoDb": "پایگاهدادهای شناسایی نشد — خودکار از والیومهای این سرویس اسنپشات میگیرد.",
+ "volumeHint": "هر والیوم این سرویس را tar میکند. زنده اجرا میشود، سازگار با کرش.",
+ "customCommandLabel": "دستور دامپ",
+ "customCommandHint": "داخل کانتینر اجرا میشود؛ stdout آن آرتیفکت میشود.",
+ "customCommandRequired": "برای روش سفارشی یک دستور دامپ وارد کنید.",
+ "advanced": "گزینههای پیشرفته",
+ "summaryTitle": "خلاصه",
+ "summaryMethod": "روش",
+ "summaryScheduleManual": "فقط دستی",
+ "summaryLogical": "دامپ {label}",
+ "summaryVolume": "اسنپشات والیوم",
+ "summaryCustom": "دستور سفارشی",
+ "retentionUnlimited": "نامحدود",
+ "keepN": "نگه داشتن {n}",
+ "olderThanN": "سقف {n}روز"
+ }
+ },
+ "cloud": {
+ "connectionGate": {
+ "reconnect": "اتصال دوباره به Openship Cloud",
+ "deployedOn": "این پروژه روی Openship Cloud مستقر است",
+ "sessionMissing": "نشست Openship Cloud شما غایب است. برای مدیریت استقرارها، دیدن لاگها و دسترسی به کنترلهای زمان اجرا برای {name} متصل شوید.",
+ "thisProject": "این پروژه",
+ "connecting": "در حال اتصال…",
+ "connect": "اتصال به Openship Cloud",
+ "readOnlyNote": "تا وقتی قطع هستید همچنان میتوانید این صفحه را فقطخواندنی مرور کنید."
+ }
+ },
+ "integrations": {
+ "githubPermission": {
+ "title": "اتصال به GitHub",
+ "subtitle": "سطح دسترسی مخزن را انتخاب کنید",
+ "intro": "انتخاب کنید Openship به کدام مخزنها دسترسی داشته باشد. بعداً میتوانید این را در تنظیمات GitHub عوض کنید.",
+ "publicTitle": "فقط مخزنهای عمومی",
+ "publicDesc": "دسترسی به مخزنهای عمومی شما. مناسب پروژههای متنباز.",
+ "publicItem1": "خواندن دادهٔ مخزن عمومی",
+ "publicItem2": "استقرار مخزنهای عمومی",
+ "publicItem3": "ایجاد وبهوک برای مخزنهای عمومی",
+ "allTitle": "مخزنهای عمومی و خصوصی",
+ "allDesc": "دسترسی کامل به همهٔ مخزنهایتان. برای پروژههای خصوصی لازم است.",
+ "allItem1": "خواندن دادهٔ مخزن عمومی و خصوصی",
+ "allItem2": "استقرار هر مخزن",
+ "allItem3": "ایجاد وبهوک برای همهٔ مخزنها",
+ "allItem4": "دسترسی به مخزنهای سازمان",
+ "noteLabel": "توجه:",
+ "noteText": "هر وقت بخواهید میتوانید دسترسی را از تنظیمات GitHub در برنامهها ← برنامههای OAuth مجاز لغو کنید.",
+ "cancel": "لغو",
+ "connecting": "در حال اتصال...",
+ "connect": "اتصال به GitHub"
+ }
+ },
+ "permissions": {
+ "resourcePicker": {
+ "failedLoadResources": "بارگذاری منابع ناموفق بود",
+ "searchPlaceholder": "جستجوی {type}...",
+ "allResources": "همهٔ {type}",
+ "noMatch": "هیچ منبعی با جستجوی شما مطابقت ندارد.",
+ "noResourcesYet": "هنوز {type} در این سازمان نیست.",
+ "selectedSummaryOne": "{count} منبع در همهٔ انواع انتخاب شده.",
+ "selectedSummaryOther": "{count} منبع در همهٔ انواع انتخاب شده.",
+ "wildcard": "وایلدکارد",
+ "coveredByAll": "پوشیدهشده توسط همه",
+ "failedLoadOrgs": "بارگذاری سازمانهای GitHub ناموفق بود",
+ "failedLoadRepos": "بارگذاری مخزنهای {login} ناموفق بود",
+ "noOrgs": "سازمان GitHubای متصل نیست.",
+ "collapse": "جمع کردن",
+ "expand": "باز کردن",
+ "entireOrg": "کل سازمان",
+ "repoCountOne": "{count} مخزن",
+ "repoCountOther": "{count} مخزن",
+ "coveredByOrg": "پوشیدهشده توسط مجوز سراسری سازمان بالا.",
+ "noRepos": "مخزنی نیست.",
+ "searchRepos": "جستجوی مخزنها…"
+ },
+ "sourceAccess": {
+ "title": "دسترسی منبع",
+ "subtitle": "این توکن چه چیزی را از {repo} میتواند بخواند",
+ "tierDeploy": "فقط استقرار",
+ "tierDeployHint": "فراداده، شاخهها و تشخیص ساخت. بدون محتوای فایل — این پیشفرض است.",
+ "tierContent": "خواندن فایلها",
+ "tierContentHint": "خواندن محتوای فایل. به مسیرهای زیر محدود کنید، یا برای همه خالی بگذارید.",
+ "tierWrite": "خواندن و نوشتن",
+ "tierWriteHint": "همچنین فایلها را تغییر بده. نیاز به مجوز نوشتن روی مخزن دارد.",
+ "readPathsLabel": "مسیرهای خواندنی",
+ "writePathsLabel": "مسیرهای نوشتنی",
+ "wholeRepo": "همه چیز",
+ "addPathPlaceholder": "src/** یا package.json",
+ "addPath": "افزودن",
+ "removePath": "حذف {path}",
+ "errorInvalidPath": "الگوی معتبر نیست. از src/** یا مسیر فایل استفاده کنید؛ .. و مسیرهای مطلق مجاز نیستند.",
+ "errorDuplicatePath": "این الگو از قبل در فهرست است.",
+ "problemWriteNeedsPermission": "دسترسی نوشتن نیاز به مجوز نوشتن روی مخزن دارد.",
+ "summaryTitle": "این مجوز اجازه میدهد",
+ "summaryDeployOnly": "استقرار و بازرسی. محتوای فایل خوانده نمیشود.",
+ "summaryReadWholeRepo": "خواندن هر فایل.",
+ "summaryReadScoped": "فقط خواندن: {paths}",
+ "summaryWriteWholeRepo": "نوشتن هر فایل.",
+ "summaryWriteScoped": "فقط نوشتن: {paths}",
+ "summaryCloneAllowed": "توکنهای کلون، تا ساختهای محلی کار کنند.",
+ "summaryCloneBlocked": "بدون توکن کلون — کلون را نمیتوان به مسیرها محدود کرد، پس ساختهای محلی کار نمیکنند.",
+ "apply": "اعمال",
+ "cancel": "لغو",
+ "subtitleAccount": "این توکن چه چیزی را از هر مخزن زیر {repo} میتواند بخواند",
+ "searchPaths": "جستجوی فایلها و پوشهها…",
+ "treeLoading": "در حال بارگذاری مخزن…",
+ "treeEmpty": "این مخزن خالی به نظر میرسد.",
+ "treeNoMatch": "فایل یا پوشهای مطابقت ندارد.",
+ "treeError": "بارگذاری درخت مخزن ممکن نشد.",
+ "treeCovered": "{path} از قبل با قانون گستردهتری بالا پوشش داده شده.",
+ "browseIn": "مرور",
+ "appliesToEveryRepo": "مسیرها به هر مخزن زیر این حساب اعمال میشوند.",
+ "selectedCount": "{count} انتخاب شده",
+ "clearPaths": "همه چیز",
+ "advancedPattern": "یا یک الگو اضافه کنید",
+ "assigningTo": "اختصاص به",
+ "surfaceRead": "خواندنی",
+ "surfaceWrite": "نوشتنی",
+ "buttonHint": "انتخاب کنید این مجوز کدام فایلها را بخواند",
+ "searchRepos": "جستجوی مخزنها…",
+ "noRepoMatch": "مخزنی مطابقت ندارد."
+ }
+ },
+ "routing": {
+ "domainSwitcher": {
+ "notConfigured": "پیکربندی نشده",
+ "selectDomain": "انتخاب دامنه",
+ "allDomains": "همهٔ دامنهها"
+ },
+ "publicEndpoints": {
+ "mappedToPort": "نگاشتشده به پورت {port}.",
+ "noPortYet": "هنوز پورتی انتخاب نشده.",
+ "mappedTo": "نگاشتشده به {path}.",
+ "exposedPort": "پورت منتشرشده",
+ "staticPath": "مسیر استاتیک",
+ "auto": "خودکار",
+ "domain": "دامنه",
+ "domainsRouted": "{count} دامنه به این اپ مسیریابی شده.",
+ "accessibleWhere": "سایت شما کجا در دسترس خواهد بود",
+ "addDomain": "افزودن دامنه",
+ "primaryDomain": "دامنهٔ اصلی",
+ "domainN": "دامنهٔ {n}",
+ "remove": "حذف"
+ },
+ "configEditor": {
+ "remove": "حذف",
+ "removeHeader": "حذف هدر",
+ "add": "افزودن",
+ "addHeader": "افزودن هدر",
+ "rewrites": "بازنویسیها",
+ "rewritesHint": "مسیر/بکاند دیگری را بدون تغییر URL سرو کنید (مثلاً /api/(.*) ← بکاند).",
+ "redirects": "تغییر مسیرها",
+ "redirectsHint": "تغییر مسیر HTTP (۳۰۱/۳۰۸ دائمی، در غیر این صورت ۳۰۷).",
+ "headers": "هدرها",
+ "headersHint": "هدرهای پاسخ اضافهشده برای مسیرهای مطابق.",
+ "permanent": "دائمی",
+ "cleanUrls": "URLهای تمیز",
+ "cleanUrlsHint": "(حذف .html)",
+ "trailingSlash": "اسلش پایانی",
+ "proxyLimits": "محدودیتهای درخواست",
+ "proxyLimitsHint": "اندازهٔ بارگذاری و مهلتها. اگر خالی بماند پیشفرضهای پروکسی اعمال میشود — بارگذاری ۱ مگابایت و مهلت ۶۰ ثانیه.",
+ "maxBodySize": "حداکثر اندازهٔ بارگذاری",
+ "maxBodySizeHint": "مثلاً 25m. درخواستهای بزرگتر با ۴۱۳ رد میشوند. پیشفرض 1m.",
+ "readTimeout": "مهلت خواندن",
+ "readTimeoutHint": "مثلاً 300s. چقدر منتظر پاسخ اپ بماند. پیشفرض ۶۰ ثانیه.",
+ "sendTimeout": "مهلت ارسال",
+ "sendTimeoutHint": "مثلاً 300s. چقدر هنگام ارسال به اپ منتظر بماند. پیشفرض ۶۰ ثانیه.",
+ "bodyTimeout": "مهلت بارگذاری",
+ "bodyTimeoutHint": "مثلاً 300s. برای بارگذاریهای بزرگ این را هم بالا ببرید، وگرنه وسط انتقال قطع میشوند. پیشفرض ۶۰ ثانیه.",
+ "proxyBuffering": "بافر کردن پاسخها",
+ "proxyBufferingHint": "(برای استریم / SSE خاموش کنید)",
+ "gzip": "فشردهسازی پاسخها",
+ "gzipHint": "(gzip متن، JSON، JS، CSS)",
+ "proxyGroups": {
+ "body": "بدنهٔ درخواست",
+ "timeouts": "مهلتهای بالادست",
+ "buffering": "بافر پاسخ",
+ "compression": "فشردهسازی",
+ "headers": "هدرها و پروتکل",
+ "upstreamTls": "TLS به اپ"
+ },
+ "proxyInherit": "پیشفرض nginx",
+ "proxyNotSet": "تنظیم نشده",
+ "proxyLiveLabel": "در حال سرو:",
+ "proxyLiveVaries": "بسته به میزبان فرق میکند",
+ "proxyDrift": "با ذخیرهشده فرق دارد",
+ "proxyDriftSummary": "لبه مقادیر متفاوتی برای اینها سرو میکند:",
+ "proxyAdoptLive": "پذیرش مقادیر زنده",
+ "proxyLiveUnavailable": "خواندن پیکربندی زنده از سرور این پروژه ممکن نشد.",
+ "proxyLiveSource": "خواندهشده از:",
+ "proxyRefreshLive": "خواندن دوبارهٔ پیکربندی زنده"
+ },
+ "settingsCard": {
+ "recordLabels": {
+ "CNAME": "ترافیک را از شبکهٔ لبه مسیریابی میکند",
+ "A": "به IP سرور شما اشاره میکند",
+ "TXT": "مالکیت دامنه را تأیید میکند"
+ },
+ "publiclyExposed": "منتشرشده بهصورت عمومی",
+ "internalOnly": "فقط داخلی",
+ "freeSubdomain": "زیردامنهٔ رایگان",
+ "free": "رایگان",
+ "customDomain": "دامنهٔ اختصاصی",
+ "custom": "اختصاصی",
+ "save": "ذخیره",
+ "checkingDns": "در حال بررسی DNS…",
+ "addRecordHint": "یک رکورد {primary} و {txt} اضافه کنید",
+ "viewRecords": "مشاهدهٔ رکوردها",
+ "redirectTo": "تغییر مسیر",
+ "redirectServeApp": "اپ را اینجا سرو کن",
+ "redirectToHost": "به {hostname}",
+ "redirectPermanent": "۳۰۱ دائمی",
+ "redirectTemporary": "۳۰۲ موقت",
+ "exposedPort": "پورت منتشرشده",
+ "staticPath": "مسیر استاتیک",
+ "staticPathHint": "فایلها را از این زیرپوشه داخل خروجی ساخت سرو کنید. برای خروجی ریشه از {path} استفاده کنید.",
+ "dnsConfiguration": "پیکربندی DNS",
+ "addRecordsFor": "این رکوردها را برای {hostname} اضافه کنید",
+ "nameHost": "نام / میزبان",
+ "valueTarget": "مقدار / مقصد",
+ "dnsHintSelfhosted": "رکورد A را به IP سرور اشاره دهید، سپس رکورد TXT را برای تأیید.",
+ "dnsHintCloud": "رکورد CNAME را برای مسیریابی اضافه کنید، سپس رکورد TXT را برای تأیید.",
+ "dnsPropagation": "تغییرات DNS ممکن است تا ۴۸ ساعت طول بکشد."
+ }
+ },
+ "updates": {
+ "severity": {
+ "critical": "بحرانی",
+ "recommended": "پیشنهادی",
+ "info": "مشورتی"
+ },
+ "viewChangelog": "مشاهدهٔ تغییرات",
+ "available": "در دسترس است",
+ "youreOn": "· شما روی {version} هستید",
+ "rerunInstall": "برای بهروزرسانی سرور خودمیزبان، نصب را دوباره اجرا کنید.",
+ "updateNow": "همین حالا بهروز کن",
+ "downloading": "در حال بارگیری Openship {version}",
+ "installing": "در حال نصب — Openship ریاستارت میشود",
+ "updateFailed": "بهروزرسانی ناموفق بود",
+ "retry": "تلاش دوباره",
+ "changelog": "تغییرات",
+ "updatedTo": "به Openship {version} بهروز شد",
+ "seeChanges": "ببینید در این انتشار چه عوض شده.",
+ "onLatest": "روی آخرین نسخه هستید.",
+ "whatsNew": "چه خبر",
+ "whatsNewIn": "چه خبر در {version}",
+ "close": "بستن",
+ "noNotes": "یادداشت انتشاری برای این نسخه نیست.",
+ "viewOnGithub": "مشاهده در GitHub",
+ "dismiss": "رد کردن"
+ }
+}
diff --git a/docs/i18n/README.ar.md b/docs/i18n/README.ar.md
index 8d55c5f3d..9a499a003 100644
--- a/docs/i18n/README.ar.md
+++ b/docs/i18n/README.ar.md
@@ -11,6 +11,7 @@
+
diff --git a/docs/i18n/README.de.md b/docs/i18n/README.de.md
index 5f47d3e70..0d2987f01 100644
--- a/docs/i18n/README.de.md
+++ b/docs/i18n/README.de.md
@@ -22,6 +22,7 @@
+
diff --git a/docs/i18n/README.es.md b/docs/i18n/README.es.md
index b48821b19..262cc2411 100644
--- a/docs/i18n/README.es.md
+++ b/docs/i18n/README.es.md
@@ -14,6 +14,7 @@
+
diff --git a/docs/i18n/README.fa.md b/docs/i18n/README.fa.md
new file mode 100644
index 000000000..ae214366e
--- /dev/null
+++ b/docs/i18n/README.fa.md
@@ -0,0 +1,169 @@
+
Openship
+
+پلتفرم متنباز و قابل میزبانی شخصی برای استقرار، با CI/CD داخلی.
یک مخزن به آن بدهید — اپ را میسازد، منتشر میکند، مسیر میدهد و TLS را تمام میکند. از اپ دسکتاپ، داشبورد وب یا CLI هدایتش کنید.
+
+
+
+
+
+
+
+
+ شروع سریع ·
+ چگونه کار میکند ·
+ رابطها ·
+ مستندات ·
+ مشارکت
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+---
+
+
+
+## شروع سریع
+
+اول یک تصمیم: **خود Openship را چگونه اجرا میکنید** (صفحهٔ کنترل). بقیهٔ کار بعد از آن یکسان است.
+
+| اگر… | Openship را اجرا کنید بهصورت | اپهای شما کجا اجرا میشوند |
+| --------------------------------------------------------------------- | ---------------------------------- | ------------------------------------------------------ |
+| **تنها هستید، یک ماشین، بدون ops** | **اپ دسکتاپ** | سروری که با SSH وصل میکنید، یا Openship Cloud |
+| **تیم هستید — یا push-to-deploy / میزبانی روی باکس خودتان میخواهید** | **سرور خودمیزبان** (`openship up`) | روی همان باکس (حالت Compose) — یا به سرور دیگر / Cloud |
+| **نمیخواهید چیزی اجرا کنید** | **Openship Cloud** | سندباکس مدیریتشده، بدون راهاندازی |
+
+> [!TIP]
+> **تنها کار میکنید؟ اپ دسکتاپ را بگیرید.** صفحهٔ کنترل فقط وقتی اپ باز است روی ماشین شما اجرا میشود — چیزی روی سرور همیشه روشن باقی نمیماند و چیزی عمومی نمیشود. نصب همیشه روشن وقتی لازم است که **push-to-deploy (CI/CD)**، **دسترسی تیمی**، یا **میزبانی اپ روی همان باکس** بخواهید.
+
+### تنها — اپ دسکتاپ
+
+صفحهٔ کنترل محلی است و سرورها را روی SSH میراند. بدون ورود، بدون ترمینال، بدون سطح عمومی — دانلود کنید، باز کنید، تمام:
+
+| پلتفرم | دانلود |
+| ------------------------- | ------------------------------------------------------------------------------------------------------------ |
+| **macOS** (Apple Silicon) | [Openship-arm64.dmg](https://github.com/oblien/openship/releases/latest/download/Openship-arm64.dmg) |
+| **macOS** (Intel) | [Openship-x64.dmg](https://github.com/oblien/openship/releases/latest/download/Openship-x64.dmg) |
+| **Windows** | [Openship-win32-x64.zip](https://github.com/oblien/openship/releases/latest/download/Openship-win32-x64.zip) |
+| **Linux** | [Openship.AppImage](https://github.com/oblien/openship/releases/latest/download/Openship.AppImage) |
+
+لینوکس: `chmod +x Openship.AppImage && ./Openship.AppImage`. از قبل CLI دارید؟ `openship install` اپ را میگیرد و اجرا میکند.
+
+### تیم / همیشه روشن — سرور خودمیزبان
+
+CLI را نصب کنید (API + داشبورد را همراه دارد)، سپس **`openship`** را اجرا کنید — ویزارد تعاملی اولین مدیر را میسازد، دامنه را وصل میکند و Openship را بهصورت سرویس بوت نصب میکند.
+
+```bash
+curl -fsSL https://get.openship.io | sh # نصب (یا: npm i -g openship — به Node 22+ نیاز دارد)
+openship # راهاندازی راهنماییشده، سپس پنل کنترل
+```
+
+برای CI / باکس بدون تعامل، ویزارد را رد کنید و مستقیم `openship up` را برانید:
+
+```bash
+openship up # نصب + شروع بهصورت سرویس پسزمینه
+openship up --public-url https://openship.example.com # + داشبورد روی دامنهٔ شما (edge + TLS)
+```
+
+**یک پروژه را مستقر کنید:**
+
+```bash
+cd your-project
+openship init # این پوشه را به یک پروژه وصل میکند
+openship deploy
+```
+
+راهنمای کامل سرور و مرجع CLI: **[openship.io/docs](https://openship.io/docs)**.
+
+---
+
+## چگونه کار میکند
+
+Openship را به یک منبع اشاره دهید — مخزن GitHub، پوشهٔ محلی، یا آرتیفکت ازپیشساخته — و یک خط لوله را تا انتها اجرا میکند:
+
+1. **تشخیص.** `package.json`، کانفیگ فریمورک، قفل وابستگیها و هر `docker-compose.yml` / `openship.json` را میخواند.
+2. **ساخت.** روی سرور مقصد یا محلی، به ایمیج Docker یا ریلیز bare.
+3. **اجرا.** بهصورت کانتینر (فقط روی loopback) یا فرایند نظارتشده روی میزبان.
+4. **مسیریابی + امنیت.** لبهٔ OpenResty یک vhost مینویسد و گواهی Let's Encrypt صادر میکند. اختلال DNS یا گواهی استقرار را شکست نمیدهد.
+5. **Push-to-deploy.** وبهوک GitHub خط لوله را روی هر push به شاخهٔ دنبالشده دوباره اجرا میکند.
+
+---
+
+## رابطها
+
+سه راه برای راندن همان بکاند:
+
+- **اپ دسکتاپ** — GUI کامل، لاگ زنده. مناسب کار انفرادی.
+- **داشبورد وب** — همان UI در مرورگر، برای تیمها.
+- **CLI** — قابل اسکریپت و مناسب CI؛ همچنین نصب نمونهٔ خودمیزبان.
+
+یک نقطهٔ پایانی **MCP** (برای ایجنتهای AI) و یک **REST API** هم برای اتوماسیون هست.
+
+> [!NOTE]
+> مستندات هنوز در حال تکمیل است. اگر چیزی کم یا مبهم است، [مشارکت](../../CONTRIBUTING.md) بسیار خوشآمد است.
+
+---
+
+## ویژگیها
+
+| | |
+| ------------------ | --------------------------------------------------------------- |
+| **CI/CD داخلی** | Push-to-deploy، محیط پیشنمایش، جریان staging/prod، بازگردانی |
+| **هر استک** | Node، Python، Go، Rust، PHP، Ruby، Java، .NET، Docker، مونوریپو |
+| **بکاند کامل** | Postgres، MySQL، MongoDB، Redis، worker، WebSockets، ذخیرهسازی |
+| **دامنه و SSL** | Let's Encrypt خودکار، wildcard، دامنهٔ نامحدود، تمدید خودکار |
+| **CDN** | کش لبه، HTTP/3، فشردهسازی Brotli، پاکسازی آنی |
+| **سرور ایمیل** | SMTP داخلی با DKIM/SPF/DMARC — بدون Mailgun یا SES |
+| **پشتیبان** | زمانبندیشده، پایگاهداده + volume، بازیابی یککلیکی |
+| **نظارت زنده** | لاگ ساخت، متریک کانتینر، جغرافیای بازدید |
+| **مقیاس** | مقیاس خودکار روی Cloud، آمادهٔ چندگره روی خودمیزبان |
+| **قابل حمل** | کانتینر استاندارد Docker — جابهجایی آزاد بین ارائهدهندهها |
+| **Docker Compose** | فایلهای compose موجود را همانطور که هستند مستقر کنید |
+
+---
+
+## وضعیت
+
+هسته آمادهٔ production است و فعال توسعه داده میشود. خودمیزبانی **رایگان** است (بدون صورتحساب).
+
+**بعدی:** خوشههای چندگره، UI توازن بار، شبکهٔ خصوصی، نظارت پیشرفته، و خط لولهٔ بصری CI/CD.
+
+---
+
+## مشارکت
+
+[CONTRIBUTING.md](../../CONTRIBUTING.md) را ببینید.
+
+---
+
+## امنیت
+
+آسیبپذیری پیدا کردید؟ لطفاً **خصوصی** گزارش دهید، هرگز در issue یا PR عمومی.
+
+- **اینجا گزارش دهید (ترجیحی):** [گزارش آسیبپذیری](https://github.com/oblien/openship/security/advisories/new)
+- دامنه و فرایند: [SECURITY.md](../../SECURITY.md).
+
+---
+
+## مجوز
+
+Openship نرمافزار **متنباز** است، تحت [Apache License 2.0](../../LICENSE).
+
+میتوانید آن را اجرا، تغییر، خودمیزبان و توزیع کنید — از جمله در محصولات تجاری — طبق شرایط Apache 2.0. متن کامل در [LICENSE](../../LICENSE).
+
+
diff --git a/docs/i18n/README.fr.md b/docs/i18n/README.fr.md
index e187a7de6..2797fef02 100644
--- a/docs/i18n/README.fr.md
+++ b/docs/i18n/README.fr.md
@@ -14,6 +14,7 @@
+
diff --git a/docs/i18n/README.ja.md b/docs/i18n/README.ja.md
index 0b71e0d0e..8ffaa6876 100644
--- a/docs/i18n/README.ja.md
+++ b/docs/i18n/README.ja.md
@@ -14,6 +14,7 @@
+
diff --git a/docs/i18n/README.ko.md b/docs/i18n/README.ko.md
index fc88e2702..42ba32d64 100644
--- a/docs/i18n/README.ko.md
+++ b/docs/i18n/README.ko.md
@@ -28,6 +28,7 @@
+
diff --git a/docs/i18n/README.pt.md b/docs/i18n/README.pt.md
index cbfe19d51..b2b89f62b 100644
--- a/docs/i18n/README.pt.md
+++ b/docs/i18n/README.pt.md
@@ -14,6 +14,7 @@
+
diff --git a/docs/i18n/README.tr.md b/docs/i18n/README.tr.md
index fd262e0eb..edf311666 100644
--- a/docs/i18n/README.tr.md
+++ b/docs/i18n/README.tr.md
@@ -22,6 +22,7 @@
+
diff --git a/docs/i18n/README.zh.md b/docs/i18n/README.zh.md
index d16dd8458..085ed07ea 100644
--- a/docs/i18n/README.zh.md
+++ b/docs/i18n/README.zh.md
@@ -14,6 +14,7 @@
+
diff --git a/packages/core/src/pricing/index.ts b/packages/core/src/pricing/index.ts
index da7f23dda..240ff706c 100644
--- a/packages/core/src/pricing/index.ts
+++ b/packages/core/src/pricing/index.ts
@@ -26,6 +26,7 @@ import copyEn from "./locales/en.json";
import copyAr from "./locales/ar.json";
import copyDe from "./locales/de.json";
import copyEs from "./locales/es.json";
+import copyFa from "./locales/fa.json";
import copyFr from "./locales/fr.json";
import copyJa from "./locales/ja.json";
import copyPt from "./locales/pt.json";
@@ -53,7 +54,7 @@ export const DEFAULT_PLAN_TIER: PlanTierId = "free";
/* ─── Locales ─────────────────────────────────────────────────────────────── */
-export const PRICING_LOCALES = ["en", "ar", "de", "es", "fr", "ja", "pt", "tr", "zh"] as const;
+export const PRICING_LOCALES = ["en", "ar", "de", "es", "fa", "fr", "ja", "pt", "tr", "zh"] as const;
export type PricingLocale = (typeof PRICING_LOCALES)[number];
/** English copy — the source of truth and the fallback for every other locale. */
@@ -72,6 +73,7 @@ const COPY: Record = {
ar: copyAr,
de: copyDe,
es: copyEs,
+ fa: copyFa,
fr: copyFr,
ja: copyJa,
pt: copyPt,
@@ -98,7 +100,7 @@ export function toPricingLocale(value: string | null | undefined): PricingLocale
* in the product is Latin (the dashboard localizes words, not numerals), so
* pin the numbering system to keep a price legible next to its own currency.
*/
-const NUMBER_LOCALE: Partial> = { ar: "ar-u-nu-latn" };
+const NUMBER_LOCALE: Partial> = { ar: "ar-u-nu-latn", fa: "fa-u-nu-latn" };
function formatCount(value: number, locale: PricingLocale): string {
return new Intl.NumberFormat(NUMBER_LOCALE[locale] ?? locale).format(value);
diff --git a/packages/core/src/pricing/locales/fa.json b/packages/core/src/pricing/locales/fa.json
new file mode 100644
index 000000000..5073e425b
--- /dev/null
+++ b/packages/core/src/pricing/locales/fa.json
@@ -0,0 +1,88 @@
+{
+ "plans": {
+ "free": {
+ "name": "رایگان",
+ "tagline": "سایتهای استاتیک را بدون هزینه روی Openship Cloud منتشر کنید."
+ },
+ "starter": {
+ "name": "شروع",
+ "tagline": "برای اولین اپ واقعی — کانتینر، پایگاهداده و بقیهٔ کارها."
+ },
+ "pro": {
+ "name": "حرفهای",
+ "tagline": "برای اپهای production که جا برای رشد میخواهند."
+ },
+ "team": {
+ "name": "مقیاس",
+ "tagline": "برای تیمهایی که ترافیک جدی را روی زیرساخت مشترک اجرا میکنند."
+ },
+ "enterprise": {
+ "name": "سازمانی",
+ "tagline": "سقف سفارشی، زیرساخت اختصاصی و SLA."
+ }
+ },
+ "features": {
+ "everythingIn": "همهٔ امکانات «{inherited}»، بهعلاوه:",
+ "staticOnly": "فقط سایتهای استاتیک — SSG، SPA و HTML ساده",
+ "anyWorkload": "هر چیزی را اجرا کنید — کانتینر، استک Compose و worker",
+ "managedServices": "Postgres و Redis مدیریتشده، با کل کاتالوگ اپها",
+ "buildMinutes": "{buildMinutes} دقیقهٔ ساخت در ماه",
+ "credits": "{credits} واحد اعتبار محاسباتی در ماه",
+ "freeSubdomains": "{freeSubdomains} زیردامنهٔ رایگان روی {freeDomainSuffix}",
+ "freeSubdomainsUnlimited": "زیردامنههای رایگان نامحدود روی {freeDomainSuffix}",
+ "customDomains": "دامنههای سفارشی نامحدود، با TLS",
+ "unlimitedSeats": "اعضای تیم نامحدود — بدون هزینهٔ بهازای نفر",
+ "previewDeploys": "استقرار پیشنمایش با هر push",
+ "backups": "پشتیبان روزانه با بازیابی به یک لحظهٔ مشخص",
+ "observability": "لاگ زنده، متریک و هشدار سلامت",
+ "mailServer": "سرور ایمیل داخلی — SMTP، IMAP و وبمیل",
+ "auditLog": "گزارش حسابرسی با نگهداری طولانیتر",
+ "sharedChannel": "کانال مشترک Slack یا Discord",
+ "sso": "SSO و SAML",
+ "customLimits": "سقفهای سفارشی که در قرارداد توافق میشود",
+ "dedicatedInfra": "زیرساخت و منطقهٔ اختصاصی",
+ "onboarding": "راهاندازی و مهاجرت با همراهی کامل تیم ما",
+ "communitySupport": "پشتیبانی جامعه",
+ "emailSupport": "پشتیبانی ایمیلی",
+ "prioritySupport": "پشتیبانی اولویتدار",
+ "dedicatedSupport": "پشتیبانی اختصاصی با SLA",
+ "selfHostedOpenSource": "کل پلتفرم، متنباز (Apache 2.0)",
+ "selfHostedUnlimited": "استقرار، پروژه، سرور و دامنهٔ نامحدود",
+ "selfHostedServices": "همهٔ سرویسهای مدیریتشده — Postgres، Redis و ایمیل",
+ "selfHostedClients": "CLI، وب و دسکتاپ — یک بکاند",
+ "selfHostedFreeSubdomains": "{freeSubdomains} زیردامنهٔ رایگان روی {freeDomainSuffix} هنگام اتصال به Openship Cloud",
+ "runningServices": "{runningServices} سرویس در حال اجرا — اپ، پایگاهداده و worker",
+ "power": "تا {powerCpu} vCPU و {powerRamGb} GB RAM برای هر سرویس",
+ "projects": "{maxProjects} پروژه",
+ "projectsUnlimited": "پروژههای نامحدود",
+ "meteredCredits": "محاسبه، پهنای باند و فضای ذخیرهسازی همگی از اعتبار ماهانه کم میشوند"
+ },
+ "selfHosted": {
+ "name": "میزبانی شخصی",
+ "tagline": "کل پلتفرم را روی ماشینهای خودتان اجرا کنید. هر باکس Linux، هر ارائهدهنده، هر منطقه. بدون اندازهگیری مصرف، بدون سقف صندلی، بدون تلهمتری.",
+ "priceLabel": "رایگان",
+ "priceNote": "Apache 2.0 — همیشه رایگان، بدون کارت اعتباری",
+ "cta": "میزبانی شخصی را شروع کنید"
+ },
+ "creditPacks": {
+ "pack_5k": "{credits} واحد اعتبار",
+ "pack_25k": "{credits} واحد اعتبار",
+ "pack_100k": "{credits} واحد اعتبار"
+ },
+ "ui": {
+ "perMonth": "/ماه",
+ "perYear": "/سال",
+ "custom": "سفارشی",
+ "free": "رایگان",
+ "unlimited": "نامحدود",
+ "mostPopular": "محبوبترین",
+ "monthsFree": "{months} ماه رایگان",
+ "ctaStart": "رایگان شروع کنید",
+ "ctaChoose": "طرح {name} را انتخاب کنید",
+ "ctaContact": "با فروش صحبت کنید",
+ "billedMonthly": "صورتحساب ماهانه، لغو در هر زمان",
+ "campaignBadge": "{percentOff}٪ تخفیف",
+ "campaignEnds": "پیشنهاد تا {date} اعتبار دارد",
+ "wasPrice": "قبلاً {price}"
+ }
+}
diff --git a/packages/core/src/pricing/pricing.test.ts b/packages/core/src/pricing/pricing.test.ts
index 6a7c78601..d15b4967d 100644
--- a/packages/core/src/pricing/pricing.test.ts
+++ b/packages/core/src/pricing/pricing.test.ts
@@ -610,9 +610,9 @@ describe("stripe price ids", () => {
});
describe("locale coverage matches the dashboard", () => {
- it("covers exactly the dashboard's 9 locales", () => {
+ it("covers exactly the dashboard's 10 locales", () => {
// Divergence would show a translated dashboard next to English prices.
- expect([...PRICING_LOCALES].sort()).toEqual(["ar", "de", "en", "es", "fr", "ja", "pt", "tr", "zh"]);
+ expect([...PRICING_LOCALES].sort()).toEqual(["ar", "de", "en", "es", "fa", "fr", "ja", "pt", "tr", "zh"]);
});
});