diff --git a/README.md b/README.md
index 2249273a5..5241696d3 100644
--- a/README.md
+++ b/README.md
@@ -36,6 +36,7 @@
+
diff --git a/apps/dashboard/src/i18n/index.ts b/apps/dashboard/src/i18n/index.ts
index 30d3ff563..3d977afc5 100644
--- a/apps/dashboard/src/i18n/index.ts
+++ b/apps/dashboard/src/i18n/index.ts
@@ -42,7 +42,7 @@ 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", "es", "fr", "de", "pt", "ja", "zh", "tr", "id"] as const;
export type Locale = (typeof locales)[number];
export const defaultLocale: Locale = "en";
export const LOCALE_COOKIE = "openship-locale";
diff --git a/apps/dashboard/src/i18n/locales/id/auth.json b/apps/dashboard/src/i18n/locales/id/auth.json
new file mode 100644
index 000000000..26703472c
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/id/auth.json
@@ -0,0 +1,111 @@
+{
+ "login": {
+ "title": "Selamat datang kembali",
+ "subtitle": "Masuk ke akun Anda untuk melanjutkan.",
+ "emailLabel": "Email",
+ "emailPlaceholder": "you@example.com",
+ "passwordLabel": "Password",
+ "passwordPlaceholder": "••••••••",
+ "forgot": "Lupa?",
+ "submit": "Masuk",
+ "submitting": "Masuk…",
+ "noAccount": "Belum punya akun?",
+ "createOne": "Buat akun"
+ },
+ "zeroAuth": {
+ "title": "Masin tidak tersedia di sini",
+ "remoteBrowser": "Instance ini berjalan tanpa akun, dan sesi hanya bisa dibuat untuk browser di mesin tempat OpenShip berjalan. Anda membuka dashboard dari {origin}, jadi tidak ada yang bisa dimasuki.",
+ "refused": "Instance ini berjalan tanpa akun, dan server menolak membuat sesi untuk browser ini, sehingga masuk di sini tidak bisa diselesaikan.",
+ "remedy": "Untuk masuk dengan akun, tambahkan ini ke environment OpenShip dan mulai ulang:",
+ "remedyAdmin": "Lalu masuk dengan akun admin yang dibuat saat pengaturan.",
+ "retry": "Coba lagi"
+ },
+ "register": {
+ "title": "Buat akun Anda",
+ "subtitle": "Mulai dengan OpenShip dalam hitungan detik.",
+ "nameLabel": "Nama",
+ "namePlaceholder": "Nama Anda",
+ "emailLabel": "Email",
+ "emailPlaceholder": "you@example.com",
+ "passwordLabel": "Password",
+ "passwordPlaceholder": "Min. 8 karakter",
+ "submit": "Buat akun",
+ "submitting": "Membuat akun…",
+ "hasAccount": "Sudah punya akun?",
+ "signIn": "Masuk"
+ },
+ "forgotPassword": {
+ "title": "Atur ulang password Anda",
+ "subtitle": "Masukkan email Anda dan kami akan mengirim kode reset.",
+ "emailLabel": "Email",
+ "emailPlaceholder": "you@example.com",
+ "submit": "Kirim kode reset",
+ "submitting": "Mengirim…",
+ "backToSignIn": "Kembali ke masuk",
+ "spamHint": "Tidak melihatnya? Periksa folder spam atau junk Anda."
+ },
+ "resetPassword": {
+ "title": "Atur password baru",
+ "subtitle": "Pilih password yang kuat untuk akun Anda.",
+ "codeSentTo": "Kami mengirim kode 6 digit ke {email}. Masukkan di bawah bersama password baru Anda.",
+ "codeSentGeneric": "Masukkan kode reset yang kami kirim ke email Anda, dan password baru Anda.",
+ "passwordLabel": "Password baru",
+ "passwordPlaceholder": "Min. 8 karakter",
+ "confirmLabel": "Konfirmasi password",
+ "confirmPlaceholder": "Masukkan ulang password",
+ "submit": "Atur ulang password",
+ "submitting": "Mengatur ulang…",
+ "backToSignIn": "Kembali ke masuk",
+ "doneTitle": "Password diatur ulang",
+ "doneDescription": "Password Anda telah berhasil diperbarui.",
+ "doneAction": "Masuk"
+ },
+ "verifyEmail": {
+ "verifyingTitle": "Memverifikasi email Anda…",
+ "verifyingSubtitle": "Sebentar saja.",
+ "verifiedTitle": "Email terverifikasi",
+ "verifiedDescription": "Email Anda telah diverifikasi. Anda bisa masuk sekarang.",
+ "verifiedAction": "Lanjutkan ke masuk",
+ "errorTitle": "Verifikasi gagal",
+ "errorDescription": "Kode mungkin salah atau sudah kedaluwarsa.",
+ "resendEmail": "Kirim ulang kode",
+ "signIn": "Masuk",
+ "pendingTitle": "Verifikasi email Anda",
+ "pendingSentTo": "Kami mengirim kode 6 digit ke {email}. Masukkan di bawah untuk memverifikasi email Anda.",
+ "pendingGeneric": "Masukkan kode verifikasi yang kami kirim ke email Anda.",
+ "resendVerification": "Kirim ulang kode",
+ "backToSignIn": "Kembali ke masuk",
+ "codeLabel": "Kode verifikasi",
+ "codePlaceholder": "Masukkan kode 6 digit",
+ "verifyCode": "Verifikasi email",
+ "codeLocked": "Terlalu banyak percobaan — kode ini dikunci. Minta kode baru dan periksa email Anda.",
+ "codeExpired": "Kode ini sudah kedaluwarsa. Minta yang baru.",
+ "codeInvalid": "Kode salah. Silakan periksa dan coba lagi.",
+ "resendRateLimited": "Silakan tunggu sebentar sebelum meminta kode lain.",
+ "spamHint": "Tidak melihat kode? Periksa folder spam atau junk Anda."
+ },
+ "oauth": {
+ "or": "atau",
+ "github": "Lanjutkan dengan GitHub",
+ "google": "Lanjutkan dengan Google"
+ },
+ "errors": {
+ "invalidCredentials": "Email atau password tidak valid.",
+ "serverUnreachable": "Tidak dapat menghubungi server. Apakah API sedang berjalan?",
+ "generic": "Terjadi kesalahan. Silakan coba lagi.",
+ "oauthFailed": "Masuk dengan OAuth gagal. Silakan coba lagi.",
+ "passwordMin": "Password harus minimal 8 karakter.",
+ "passwordMismatch": "Password tidak cocok.",
+ "invalidToken": "Kode reset tidak valid atau hilang. Silakan minta kode baru.",
+ "resetFailed": "Tidak dapat mengatur ulang password.",
+ "createFailed": "Tidak dapat membuat akun.",
+ "verificationFailed": "Verifikasi gagal.",
+ "verificationExpired": "Verifikasi gagal. Kode mungkin sudah kedaluwarsa.",
+ "resendFailed": "Gagal mengirim ulang. Silakan coba lagi.",
+ "verificationSent": "Email verifikasi terkirim!"
+ },
+ "back": "Kembali",
+ "toggleTheme": "Ubah tema",
+ "showPassword": "Tampilkan password",
+ "hidePassword": "Sembunyikan password"
+}
diff --git a/apps/dashboard/src/i18n/locales/id/autoDns.json b/apps/dashboard/src/i18n/locales/id/autoDns.json
new file mode 100644
index 000000000..60e019f17
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/id/autoDns.json
@@ -0,0 +1,29 @@
+{
+ "checking": "Memeriksa penyedia DNS Anda…",
+ "noProviderTitle": "Atur record ini secara otomatis",
+ "noProviderDesc": "Hubungkan penyedia DNS dan OpenShip bisa menambahkan record ini untuk Anda.",
+ "connect": "Hubungkan penyedia DNS",
+ "unauthorizedTitle": "Penedia DNS perlu dihubungkan ulang",
+ "unauthorizedDesc": "Token yang disimpan ditolak. Hubungkan ulang untuk mengonfigurasi DNS otomatis.",
+ "reconnect": "Hubungkan ulang penyedia",
+ "unavailableTitle": "Tidak dapat menghubungi penyedia DNS Anda",
+ "unavailableDesc": "Kami tidak dapat memeriksa zona Anda saat ini. Coba lagi sebentar.",
+ "retry": "Coba lagi",
+ "managedBy": "{provider} mengelola {zone}",
+ "apply": "Konfigurasi DNS otomatis",
+ "applying": "Mengonfigurasi…",
+ "reapply": "Terapkan ulang record",
+ "allInPlace": "Semua record sudah terpasang",
+ "actionCreate": "Tambah",
+ "actionUpdate": "Perbarui",
+ "actionAdopt": "Arahkan ulang",
+ "actionInSync": "Sudah terpasang",
+ "actionConflict": "Konflik",
+ "conflictNote": "Tidak dikelola oleh OpenShip — hapus atau atur record ini secara manual.",
+ "outcomeApplied": "Diterapkan",
+ "outcomeSkipped": "Dilewati",
+ "outcomeFailed": "Gagal",
+ "resultDone": "DNS diperbarui.",
+ "resultPartial": "Beberapa record perlu diperhatikan.",
+ "applyFailed": "Tidak dapat mengonfigurasi DNS."
+}
diff --git a/apps/dashboard/src/i18n/locales/id/billing.json b/apps/dashboard/src/i18n/locales/id/billing.json
new file mode 100644
index 000000000..ecf796163
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/id/billing.json
@@ -0,0 +1,215 @@
+{
+ "capacity": {
+ "title": "Kapasitas & penggunaan",
+ "subtitle": "Kapasitas yang disertakan dalam paket Anda dan berapa yang telah Anda gunakan. Angka langsung disinkronkan dari OpenShip Cloud.",
+ "routes": "Route gratis",
+ "runningServices": "Layanan aktif",
+ "projects": "Proyek",
+ "workspaces": "Workspace",
+ "unlimited": "Tidak terbatas",
+ "used": "digunakan",
+ "of": "dari {max}",
+ "syncing": "Menyinkronkan dari cloud"
+ },
+ "topupsComingSoon": "Isi ulang kredit satu kali segera hadir — Anda akan bisa menambah kredit di atas kuota bulanan Anda di sini.",
+ "header": {
+ "credits": "Komputasi",
+ "creditsSuffix": "dari {limit}",
+ "overQuota": "Melebihi kuota",
+ "bandwidth": "Bandwidth",
+ "bandwidthNote": "gabungan masuk + keluar",
+ "build": "Waktu build",
+ "min": "mnt",
+ "capacity": "Kapasitas",
+ "workspaces": "workspace",
+ "vcpus": "vCPU",
+ "ram": "RAM",
+ "diskCap": "disk"
+ },
+ "tabs": {
+ "overview": "Ringkasan",
+ "usage": "Penggunaan",
+ "plans": "Paket",
+ "topups": "Isi Ulang",
+ "payment": "Metode Pembayaran",
+ "invoices": "Faktur"
+ },
+ "portal": {
+ "openButton": "Buka Portal Stripe",
+ "errorMissingUrl": "URL portal hilang dari respons",
+ "errorOpenFailed": "Gagal membuka portal Stripe"
+ },
+ "unavailable": {
+ "notConnected": {
+ "title": "Hubungkan ke OpenShip Cloud untuk mengelola tagihan",
+ "description": "Instance lokal Anda belum terhubung ke OpenShip Cloud. Hubungkan untuk melihat status langganan, penggunaan, faktur, dan isi ulang.",
+ "connect": "Hubungkan ke OpenShip Cloud",
+ "connecting": "Menunggu masuk…"
+ },
+ "sessionExpired": {
+ "title": "Sesi OpenShip Cloud Anda kedaluwarsa",
+ "description": "Anda masih terhubung, tetapi token sesi yang dikeluarkan OpenShip Cloud tidak lagi valid. Hubungkan ulang untuk memperbaruinya — tidak perlu memutuskan hubungan terlebih dahulu.",
+ "reconnect": "Hubungkan ulang ke OpenShip Cloud",
+ "connecting": "Menunggu masuk…"
+ },
+ "unreachable": {
+ "title": "Tidak dapat menghubungi tagihan OpenShip Cloud",
+ "description": "Sesi cloud Anda mungkin kedaluwarsa atau cloud sementara tidak tersedia. Coba lagi, atau hubungkan ulang dari Pengaturan → Cloud.",
+ "tryAgain": "Coba lagi",
+ "reconnect": "Hubungkan ulang"
+ },
+ "notEnabled": {
+ "title": "Tagihan tidak diaktifkan untuk organisasi ini",
+ "description": "Tagihan dikonfigurasi tetapi tidak aktif untuk organisasi ini. Hubungi administrator Anda untuk mengaktifkannya."
+ }
+ },
+ "plansRoute": {
+ "loadError": "Gagal memuat paket",
+ "genericError": "Terjadi kesalahan",
+ "tryAgain": "Coba lagi",
+ "checkoutError": "Gagal memulai checkout"
+ },
+ "pricing": {
+ "custom": "Kustom",
+ "comingSoon": "Segera hadir",
+ "mostPopular": "Paling populer",
+ "currentPlan": "Paket saat ini",
+ "contactSales": "Hubungi sales",
+ "freeForever": "Gratis selamanya",
+ "upgrade": "Tingkatkan"
+ },
+ "topups": {
+ "loadError": "Gagal memuat paket isi ulang",
+ "title": "Isi ulang kredit",
+ "description": "Paket kredit satu kali yang dibebankan melalui Stripe. Kredit tidak pernah kedaluwarsa dan ditumpuk di atas kuota bulanan Anda.",
+ "tryAgain": "Coba lagi",
+ "credits": "menit komputasi",
+ "redirecting": "Mengalihkan…",
+ "buy": "Beli",
+ "empty": "Belum ada paket isi ulang yang tersedia.",
+ "receiptsTitle": "Isi ulang terbaru",
+ "receiptsDescription": "Kwitansi dikelola di Stripe — buka portal tagihan untuk melihat riwayat faktur dan mengunduh kwitansi.",
+ "opening": "Membuka…",
+ "openPortal": "Buka Portal Stripe",
+ "checkoutError": "Gagal memulai checkout",
+ "portalError": "Gagal membuka portal tagihan"
+ },
+ "overview": {
+ "creditsLeft": "menit komputasi tersisa",
+ "planLabel": "Paket {name}",
+ "usedThisPeriod": "Digunakan periode ini",
+ "resets": "Reset",
+ "resetsInDay": "Dalam {n} hari",
+ "resetsInDays": "Dalam {n} hari",
+ "none": "—",
+ "recentActivity": "Aktivitas terbaru",
+ "last7Days": "7 hari terakhir",
+ "viewFullUsage": "Lihat penggunaan lengkap",
+ "noUsageYet": "Belum ada penggunaan",
+ "usageError": "Gagal memuat penggunaan",
+ "needMoreCredits": "Butuh lebih banyak menit komputasi?",
+ "oneTimeTopups": "Isi ulang satu kali, tanpa perubahan langganan",
+ "seeAllPacks": "Lihat semua paket",
+ "packsError": "Gagal memuat paket",
+ "noPacks": "Tidak ada paket isi ulang yang tersedia.",
+ "creditsAmount": "{n} menit komputasi",
+ "oneTime": "{price} satu kali",
+ "buy": "Beli",
+ "checkoutError": "Gagal memulai checkout"
+ },
+ "usage": {
+ "loadError": "Gagal memuat penggunaan",
+ "kpi": {
+ "balance": "Saldo",
+ "balanceSuffix": "dari {limit} kredit",
+ "consumed": "Digunakan periode ini",
+ "credits": "menit komputasi",
+ "burnRate": "Laju pembakaran",
+ "burnRateSuffix": "kredit/hari (7 hari terakhir)",
+ "projected": "Proyeksi akhir periode",
+ "noPeriod": "tidak ada periode"
+ },
+ "granularity": {
+ "day": "hari",
+ "week": "minggu"
+ },
+ "chart": {
+ "title": "Penggunaan dari waktu ke waktu",
+ "subtitle": "Unit terukur bertumpuk per {unit}."
+ },
+ "breakdown": {
+ "title": "Rincian sumber daya",
+ "subtitle": "Kredit ditetapkan secara proporsional ke penggunaan sumber daya terukur dalam rentang yang dipilih.",
+ "resource": "Sumber Daya",
+ "units": "Unit",
+ "credits": "Menit Komputasi",
+ "percentOfTotal": "% dari total",
+ "loading": "Memuat…",
+ "empty": "Tidak ada penggunaan dalam rentang yang dipilih."
+ },
+ "resources": {
+ "cpu": {
+ "label": "Waktu CPU",
+ "units": "vCPU-jam"
+ },
+ "memory": {
+ "label": "Memori",
+ "units": "GB-jam"
+ },
+ "disk": {
+ "label": "Disk IO",
+ "units": "GB"
+ },
+ "network": {
+ "label": "Jaringan",
+ "units": "GB"
+ }
+ },
+ "toolbar": {
+ "workspace": "Workspace",
+ "workspaceTooltip": "Rincian per workspace hadir di v1.1",
+ "allWorkspaces": "Semua workspace",
+ "to": "ke"
+ },
+ "empty": "Tidak ada data penggunaan untuk rentang yang dipilih."
+ },
+ "chart": {
+ "cpu": "CPU",
+ "memory": "Memori",
+ "diskIo": "Disk IO",
+ "network": "Jaringan",
+ "weekTick": "Mg {n}"
+ },
+ "layout": {
+ "title": "Tagihan & Penggunaan",
+ "subtitle": "Kelola langganan, penggunaan, dan metode pembayaran Anda."
+ },
+ "sidebar": {
+ "planSuffix": "Paket {name}",
+ "status": "Status",
+ "statusInactive": "Tidak aktif",
+ "upgradeTo": "Tingkatkan ke {name}",
+ "contactSales": "Hubungi sales",
+ "freeForever": "Gratis selamanya",
+ "perMonth": "${price}/bln",
+ "statuses": {
+ "active": "Aktif",
+ "trialing": "Uji coba",
+ "past_due": "Terlambat",
+ "canceled": "Dibatalkan",
+ "incomplete": "Tidak lengkap",
+ "incomplete_expired": "Kedaluwarsa",
+ "unpaid": "Belum dibayar",
+ "paused": "Dijeda"
+ },
+ "includedTitle": "Yang termasuk"
+ },
+ "paymentPanel": {
+ "title": "Metode pembayaran",
+ "description": "Kartu, rekening bank, dan detail tagihan dikelola di Portal Pelanggan Stripe. Buka portal untuk menambah, menghapus, atau memperbarui metode pembayaran Anda."
+ },
+ "invoicesPanel": {
+ "title": "Faktur",
+ "description": "Faktur dan kwitansi berada di Portal Pelanggan Stripe. Buka portal untuk melihat riwayat tagihan Anda dan mengunduh faktur PDF."
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/id/brand.json b/apps/dashboard/src/i18n/locales/id/brand.json
new file mode 100644
index 000000000..14bfbc0fd
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/id/brand.json
@@ -0,0 +1,3 @@
+{
+ "brand": "OpenShip"
+}
diff --git a/apps/dashboard/src/i18n/locales/id/chrome.json b/apps/dashboard/src/i18n/locales/id/chrome.json
new file mode 100644
index 000000000..e95d6fb31
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/id/chrome.json
@@ -0,0 +1,86 @@
+{
+ "accountSwitcher": {
+ "workspacesCount": "{count} workspace",
+ "newWorkspace": "Workspace baru",
+ "createTitle": "Buat workspace",
+ "createDescription": "Organisasi terpisah untuk proyek, deployment, server, dan anggota.",
+ "nameLabel": "Nama",
+ "namePlaceholder": "Acme Corp",
+ "cancel": "Batal",
+ "create": "Buat",
+ "toastTitle": "Organisasi",
+ "switchFailed": "Gagal beralih",
+ "createFailed": "Gagal membuat"
+ },
+ "sidebar": {
+ "workspaceFallback": "Workspace",
+ "workspacesCount": "{count} workspace",
+ "switchOrganization": "Beralih organisasi",
+ "current": "Saat ini",
+ "cloudLabel": "Cloud: {email}",
+ "linkedToCloud": "Tersambung ke OpenShip Cloud sebagai {email}",
+ "backToSetup": "Kembali ke pengaturan",
+ "personal": "Pribadi"
+ },
+ "mailRail": {
+ "selectServer": "Pilih server email",
+ "switchServer": "Beralih server email",
+ "setupIncomplete": "Pengaturan belum lengkap"
+ },
+ "migration": {
+ "inProgressTitle": "Migrasi sedang berlangsung",
+ "inProgressBody": "Data Anda sedang dipindahkan. Halaman ini akan menyegarkan setelah pemotongan selesai — biasanya 30-90 detik.",
+ "inProgressHint": "Jangan tutup tab ini. Kami memeriksa status migrasi setiap beberapa detik dan akan memuat ulang secara otomatis.",
+ "newLocation": "Lokasi baru",
+ "switchBackPrompt": "Butuh mode single-user kembali di mesin ini?",
+ "switchBackAction": "Kembali ke single user",
+ "cloudTitle": "Instance ini dipindahkan ke OpenShip Cloud",
+ "cloudBody": "Tim Anda sekarang berkolaborasi di URL di bawah. Instance lokal ini tidak lagi menyimpan proyek, pengaturan, atau deployment Anda — buka dashboard cloud untuk melanjutkan.",
+ "cloudCta": "Buka dashboard cloud",
+ "tunneledTitle": "OpenShip Anda sekarang dapat diakses melalui tunnel",
+ "tunneledBody": "Rekan tim bisa masuk di URL di bawah ketika mesin Anda online. Data masih tinggal di perangkat ini — tunnel hanya mengeksposnya melalui edge Oblien.",
+ "tunneledCta": "Buka URL tunnel",
+ "serverTitle": "Instance ini dipindahkan ke server Anda",
+ "serverBody": "Tim Anda sekarang berkolaborasi di URL di bawah. Instance lokal ini tidak lagi menyimpan proyek, pengaturan, atau deployment Anda — buka dashboard tim untuk melanjutkan.",
+ "serverCta": "Buka dashboard tim"
+ },
+ "notFound": {
+ "title": "Halaman tidak ditemukan",
+ "descGlobal": "Halaman yang Anda cari tidak ada atau sudah dipindahkan.",
+ "descDashboard": "Halaman ini tidak ada atau mungkin sudah dipindahkan. Periksa URL, atau kembali ke dashboard Anda.",
+ "backToDashboard": "Kembali ke dashboard",
+ "viewDeployments": "Lihat deployment",
+ "copyId": "Salin ID"
+ },
+ "error": {
+ "title": "Halaman ini mengalami kesalahan",
+ "description": "Sesuatu di layar ini gagal dirender. Mencoba lagi biasanya mengatasinya — tidak ada yang Anda deploy yang terpengaruh.",
+ "tryAgain": "Coba lagi",
+ "goToProjects": "Ke proyek",
+ "refCode": "Ref"
+ },
+ "apiDown": {
+ "title": "Tidak dapat menghubungi API",
+ "description": "Dashboard sedang berjalan, tetapi tidak dapat berkomunikasi dengan API OpenShip — jadi belum ada yang ditampilkan. Deployment Anda tetap berjalan.",
+ "hintStatus": "Periksa apakah API aktif: jalankan `openship status` di host.",
+ "hintStart": "Tidak berjalan? Mulai dengan `openship up`.",
+ "hintRemote": "Mengelola server remote? Pastikan URL-nya dan port API dapat diakses dari sini.",
+ "retry": "Coba lagi",
+ "docs": "Dokumentasi self-hosting"
+ },
+ "errorLinks": {
+ "docs": "Dokumentasi",
+ "github": "Laporkan masalah"
+ },
+ "window": {
+ "minimize": "Perkecil",
+ "maximize": "Perbesar",
+ "restore": "Pulihkan",
+ "close": "Tutup",
+ "navigation": "Navigasi",
+ "back": "Kembali",
+ "forward": "Maju",
+ "reload": "Muat ulang",
+ "devTools": "Alat pengembang"
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/id/dashboard.json b/apps/dashboard/src/i18n/locales/id/dashboard.json
new file mode 100644
index 000000000..e2dc35727
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/id/dashboard.json
@@ -0,0 +1,221 @@
+{
+ "home": {
+ "goodMorning": "Selamat pagi",
+ "goodAfternoon": "Selamat siang",
+ "goodEvening": "Selamat malam",
+ "subtitle": "Berikut yang terjadi di proyek Anda",
+ "statsProjects": "Proyek",
+ "statsDeployments": "Deployment",
+ "statsSuccessful": "Berhasil",
+ "statsFailed": "Gagal",
+ "recentProjects": "Proyek Terbaru",
+ "yourLatestActivity": "Aktivitas terbaru Anda",
+ "viewAll": "Lihat semua",
+ "noProjectsYet": "Belum ada proyek",
+ "createFirstProject": "Buat proyek pertama Anda",
+ "justNow": "baru saja",
+ "minutesAgo": "{n}m lalu",
+ "hoursAgo": "{n}j lalu",
+ "daysAgo": "{n}h lalu",
+ "monthsAgo": "{n}bln lalu",
+ "statusLive": "Aktif",
+ "statusPaused": "Dijeda",
+ "yourProjects": "Proyek Anda",
+ "loading": "Memuat…",
+ "projectCountOne": "{count} proyek",
+ "projectCountOther": "{count} proyek",
+ "viewAllProjects": "Lihat semua {count} proyek →",
+ "viewAllApps": "Lihat semua {count} aplikasi →",
+ "greetingName": "{greeting}, {name}",
+ "importGit": "Impor Git",
+ "importGitDesc": "Dari repository",
+ "mcpDeploy": "MCP Deploy",
+ "mcpDeployDesc": "Hubungkan AI agent",
+ "settingsCard": "Pengaturan",
+ "settingsCardDesc": "Akun & tim",
+ "docs": "Dokumentasi",
+ "docsDesc": "Pelajari lebih lanjut",
+ "activityTitle": "Aktivitas",
+ "systemStatus": "Status sistem",
+ "operational": "Operasional",
+ "oneIssue": "1 masalah",
+ "manyIssues": "{n} masalah",
+ "recentTitle": "Terbaru",
+ "activityEmpty": "Aktivitas Anda akan muncul di sini",
+ "appsEmptyTitle": "Belum ada aplikasi",
+ "appsEmptyDesc": "Satu klik, self-hosted penuh — database, otomatisasi, analitik, vault password, dan lainnya, berjalan di server Anda sendiri.",
+ "allSystemsOperational": "Semua sistem operasional",
+ "runningSmoothly": "Proyek Anda berjalan dengan lancar",
+ "time": {
+ "justNow": "baru saja",
+ "minutes": "{n}m lalu",
+ "hours": "{n}j lalu",
+ "days": "{n}h lalu",
+ "months": "{n}bln lalu"
+ }
+ },
+ "nav": {
+ "home": "Beranda",
+ "new-project": "Proyek Baru",
+ "projects": "Proyek",
+ "apps": "Aplikasi",
+ "deployments": "Deployment",
+ "issues": "Masalah",
+ "backups": "Backup",
+ "domains": "Domain",
+ "monitoring": "Monitoring",
+ "servers": "Server",
+ "emails": "Email",
+ "jobs": "Job",
+ "settings": "Pengaturan",
+ "billing": "Tagihan & Penggunaan",
+ "audit": "Log audit",
+ "mailSetup": "Atur email",
+ "mailSetupProgress": "Proses pengaturan",
+ "addMailbox": "Tambah mailbox",
+ "webmail": "Webmail",
+ "sections": {
+ "main": "Utama",
+ "settings": "Pengaturan",
+ "infrastructure": "Infrastruktur",
+ "mail": "Email",
+ "delivery": "Pengiriman",
+ "system": "Sistem",
+ "account": "Akun"
+ }
+ },
+ "sidebar": {
+ "expand": "Perluas sidebar",
+ "collapse": "Ciutkan sidebar"
+ },
+ "user": {
+ "logout": "Keluar",
+ "loggingOut": "Keluar…",
+ "profile": "Profil",
+ "preferences": "Preferensi"
+ },
+ "pages": {
+ "apps": {
+ "title": "Aplikasi",
+ "description": "Layanan terkelola satu klik — instal dan jalankan Convex, WordPress, email, dan lainnya.",
+ "createButton": "Instal Aplikasi",
+ "loading": "Memuat aplikasi…",
+ "countOne": "{count} aplikasi terinstal",
+ "countOther": "{count} aplikasi terinstal",
+ "emptyTitle": "Instal aplikasi pertama Anda",
+ "emptyDescription": "Jalankan Convex, n8n, Ghost, dan lainnya — satu klik, self-hosted penuh. Pilih satu untuk memulai.",
+ "popular": "Aplikasi populer",
+ "alsoDeploy": "Anda juga bisa deploy",
+ "browseAll": "Jelajahi semua aplikasi",
+ "moreApps": "+{count} lagi",
+ "catalogTitle": "Instal aplikasi",
+ "catalogDescription": "Layanan terkelola satu klik. Pilih satu untuk menginstalnya sebagai aplikasi.",
+ "catalogSearchPlaceholder": "Cari aplikasi…",
+ "catalogNoResults": "Tidak ada aplikasi yang cocok dengan pencarian Anda.",
+ "categories": {
+ "all": "Semua",
+ "backend": "Backend",
+ "database": "Database",
+ "cms": "CMS",
+ "analytics": "Analitik",
+ "automation": "Otomatisasi",
+ "mail": "Email",
+ "other": "Alat"
+ },
+ "installing": "Menginstal…",
+ "install": "Instal",
+ "cancel": "Batal",
+ "nameLabel": "Nama",
+ "configTitle": "Konfigurasi {app}",
+ "comingSoon": "Segera hadir"
+ },
+ "projects": {
+ "title": "Proyek",
+ "description": "Deploy dan kelola aplikasi Anda dengan mudah",
+ "createButton": "Buat Proyek",
+ "stats": {
+ "totalProjects": "Total Proyek",
+ "liveNow": "Aktif Sekarang",
+ "avgPerDay": "Rata-rata per Hari",
+ "successRate": "Tingkat Keberhasilan"
+ },
+ "searchPlaceholder": "Cari proyek…",
+ "pinnedProjects": "Proyek yang Disematkan",
+ "otherProjects": "Proyek Lainnya",
+ "noResultsFound": "Tidak ada proyek yang cocok dengan \"{query}\"",
+ "emptyState": {
+ "title": "Mulai proyek pertama Anda",
+ "description": "Impor repository Git, gunakan template, atau deploy dari CLI. Proyek Anda akan muncul di sini.",
+ "createProject": "Buat Proyek",
+ "browseTemplates": "Jelajahi Template",
+ "zeroConfig": "Deployment zero-config",
+ "instant": "Instan",
+ "instantDesc": "Deploy dalam hitungan detik",
+ "global": "Global",
+ "globalDesc": "100+ region edge",
+ "previews": "Pratinjau",
+ "previewsDesc": "URL per branch",
+ "rollbacks": "Rollback",
+ "rollbacksDesc": "Kembalikan satu klik",
+ "commandPalette": "Atau tekan {key} untuk membuka command palette"
+ },
+ "view": {
+ "label": "Tampilan",
+ "list": "Daftar",
+ "grid": "Grid"
+ }
+ },
+ "deployments": {
+ "title": "Deployment",
+ "description": "Lihat dan kelola riwayat deployment Anda."
+ },
+ "domains": {
+ "title": "Domain",
+ "description": "Kelola domain kustom dan record DNS."
+ },
+ "monitoring": {
+ "title": "Monitoring",
+ "description": "Log, metrik, dan health check untuk layanan Anda."
+ },
+ "servers": {
+ "title": "Server",
+ "description": "Kelola server deployment dan infrastruktur Anda."
+ },
+ "settings": {
+ "title": "Pengaturan",
+ "description": "Pengaturan akun, tim, dan platform."
+ },
+ "billing": {
+ "title": "Tagihan & Penggunaan",
+ "description": "Kelola langganan, penggunaan, dan metode pembayaran Anda."
+ }
+ },
+ "quickLinks": {
+ "title": "Aksi Cepat",
+ "subtitle": "Langsung ke tugas umum",
+ "deployProject": {
+ "title": "Deploy Proyek",
+ "description": "Impor dari Git"
+ },
+ "projects": {
+ "title": "Proyek",
+ "description": "Semua proyek"
+ },
+ "deployments": {
+ "title": "Deployment",
+ "description": "Riwayat build"
+ },
+ "settings": {
+ "title": "Pengaturan",
+ "description": "Kelola akses"
+ },
+ "domains": {
+ "title": "Domain",
+ "description": "Domain kustom"
+ },
+ "billing": {
+ "title": "Tagihan & Penggunaan",
+ "description": "Penggunaan & paket"
+ }
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/id/deploy.json b/apps/dashboard/src/i18n/locales/id/deploy.json
new file mode 100644
index 000000000..db2a265f5
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/id/deploy.json
@@ -0,0 +1,316 @@
+{
+ "page": {
+ "projectNameLabel": "Nama Proyek",
+ "projectNameHint": "Pengenal unik untuk deployment Anda",
+ "uploadedFolder": "Folder yang diunggah",
+ "errorInvalidUrlTitle": "URL Repository Tidak Valid",
+ "errorInvalidUrlDetails": "Format URL repository tidak dikenali. Periksa URL dan coba lagi.",
+ "errorLoadProjectTitle": "Gagal Memuat Proyek",
+ "errorLoadRepoTitle": "Gagal Memuat Repository",
+ "errorScanFolderFailed": "Kami tidak dapat memindai folder ini. Pastikan path benar dan dapat diakses.",
+ "errorLoadRepoFailed": "Kami tidak dapat memuat repository ini. Repository mungkin privat, tidak ada, atau Anda tidak memiliki akses ke repository tersebut."
+ },
+ "header": {
+ "private": "Privat",
+ "public": "Publik",
+ "help": "Bantuan"
+ },
+ "domainSettings": {
+ "saveFailed": "Gagal menyimpan domain",
+ "toastTitle": "Domain",
+ "routeFreeLabel": "Subdomain gratis",
+ "routeFreeDesc": "URL {domain} gratis, siap digunakan secara instan.",
+ "routeCustomLabel": "Domain kustom",
+ "routeCustomDesc": "Arahkan domain Anda sendiri ke deployment ini.",
+ "routeNoneLabel": "Tidak ada",
+ "routeNoneDesc": "Tidak ada URL publik — hanya internal. Akses melalui port di host."
+ },
+ "dns": {
+ "title": "Konfigurasi DNS",
+ "addRecordsFor": "Tambahkan record ini di penyedia DNS Anda untuk",
+ "selfInfoPre": "Arahkan ",
+ "recordA": "record A",
+ "selfInfoMid": " ke IP server Anda — HTTPS diterbitkan secara otomatis pada deployment pertama.",
+ "recordTxt": "record TXT",
+ "verifySuffix": " untuk verifikasi kepemilikan.",
+ "cloudInfoPre": "Tambahkan ",
+ "recordCname": "record CNAME",
+ "cloudInfoMid": " untuk routing, lalu tambahkan ",
+ "modalTitle": "Arahkan domain Anda, lalu deploy",
+ "modalSubtitle": "HTTPS diterbitkan pada deployment pertama setelah domain Anda mengarah ke sini — atau lewati dan tambahkan DNS nanti dari tab Domain.",
+ "autoConfigTitle": "Konfigurasi otomatis dengan penyedia DNS saya",
+ "autoConfigDesc": "Hubungkan registrar Anda dan kami akan menambahkan record ini untuk Anda.",
+ "comingSoon": "Segera hadir",
+ "deployAction": "Deploy",
+ "cancel": "Batal",
+ "loadingRecords": "Memuat record DNS…",
+ "serverIpPlaceholder": "IP publik server Anda",
+ "record": {
+ "purposeA": "Mengarahkan {hostname} ke IP server ini",
+ "purposeCname": "Merutekan {hostname} melalui jaringan edge",
+ "purposeCnameAlias": "Mengarahkan {hostname} ke {target}",
+ "purposeTxt": "Membuktikan Anda memiliki {hostname} — kami memeriksa record ini saat Anda menekan Verifikasi",
+ "fieldType": "Tipe",
+ "fieldName": "Nama",
+ "fieldValue": "Nilai",
+ "fieldTtl": "TTL",
+ "ttlValue": "Otomatis (atau 3600)",
+ "apexHint": "\"@\" adalah domain itu sendiri — beberapa penyedia menginginkan kolom ini dikosongkan",
+ "fullName": "Nama lengkap, jika penyedia Anda membutuhkannya:",
+ "proxyNote": "Nonaktifkan saklar proxy/CDN (Cloudflare: awan abu-abu, \"DNS only\") agar HTTPS dapat diterbitkan untuk domain ini.",
+ "copy": "Salin"
+ }
+ },
+ "buildSummary": {
+ "title": "Ringkasan Deployment",
+ "domain": "Domain",
+ "extraMore": " +{count} lagi",
+ "services": "Layanan",
+ "servicesValue": "{total} total · {exposed} terekspos",
+ "buildLocation": "Lokasi Build",
+ "framework": "Framework",
+ "app": "Aplikasi",
+ "install": "Instalasi",
+ "build": "Build",
+ "output": "Output",
+ "stack": "Stack",
+ "runtime": "Runtime",
+ "port": "Port",
+ "localMachine": "Mesin Lokal",
+ "cloud": "Openship Cloud",
+ "server": "Server"
+ },
+ "checklist": {
+ "title": "Daftar Periksa Deployment",
+ "servicesDetected": "Layanan terdeteksi",
+ "servicesCount": "{count} layanan",
+ "publicDomains": "Domain publik",
+ "exposedOf": "{exposed} dari {exposable} terekspos",
+ "canBeExposed": "{count} dapat diekspos",
+ "buildServices": "Layanan build",
+ "toBuild": "{count} untuk dibangun",
+ "environment": "Lingkungan",
+ "varsAcross": "{vars} variabel di {services} layanan",
+ "noEnvVars": "Tidak ada env vars yang diatur",
+ "domains": "Domain"
+ },
+ "sidebar": {
+ "copyCloneToken": "Salin token clone",
+ "cloneTokenTitle": "Token clone",
+ "cloneTokenNoRepo": "Tidak ada repository GitHub untuk deployment ini.",
+ "cloneTokenCopiedTitle": "Token clone disalin",
+ "cloneTokenCopied": "Perintah git clone disalin. Token yang tersemat kedaluwarsa dalam satu jam.",
+ "cloneTokenFailed": "Tidak dapat membuat token clone",
+ "newEnvironment": "Lingkungan baru",
+ "selectBranch": "Pilih branch",
+ "saving": "Menyimpan…",
+ "saveChanges": "Simpan perubahan",
+ "deploying": "Mendeploy…",
+ "deploy": "Deploy",
+ "freeDomainFeature": "Menggunakan domain .{domain} gratis di server Anda sendiri",
+ "freeDomainDesc": "Domain .{domain} gratis dirutekan melalui Openship Cloud. Untuk mendeploy proyek ini ke server Anda sendiri, hubungkan Openship Cloud atau ubah proyek ini ke domain kustom.",
+ "freeDomainHint": "Jika Anda tidak ingin menghubungkan Openship Cloud, ubah domain proyek dari gratis ke kustom sebelum deploy.",
+ "servicesFreeDomainFeature": "Menggunakan domain .{domain} gratis untuk layanan Anda",
+ "servicesFreeDomainDesc": "Satu atau lebih layanan yang terekspos menggunakan domain .{domain} gratis. Untuk mendeploy ke server Anda sendiri, hubungkan Openship Cloud atau ubah layanan tersebut ke domain kustom.",
+ "servicesFreeDomainHint": "Domain kustom berfungsi tanpa Openship Cloud. Domain managed gratis tidak berfungsi.",
+ "unreachableTitle": "Tidak ada layanan publik yang dapat dijangkau",
+ "unreachableBody": "Stack ini memiliki layanan aplikasi dengan port, tetapi tidak ada yang dikonfigurasi dengan domain publik. Jika Anda deploy sekarang, layanan dapat berjalan secara internal, tetapi pengguna tidak akan dapat mengaksesnya dari URL.",
+ "beforeDeploying": "Sebelum deploy",
+ "unreachableStep1": "Pilih layanan yang seharusnya publik.",
+ "unreachableStep2": "Aktifkan eksposur untuk layanan tersebut.",
+ "unreachableStep3": "Atur subdomain atau domain kustom dan pilih port publik.",
+ "reviewServices": "Tinjau Layanan",
+ "deployAnyway": "Deploy Tetap Saja"
+ },
+ "skeleton": {
+ "settings1": "Memuat pengaturan yang tersimpan",
+ "settings2": "Memulihkan konfigurasi",
+ "local1": "Memindai proyek",
+ "local2": "Membaca manifest",
+ "local3": "Mendeteksi framework",
+ "repo1": "Menghubungkan ke GitHub",
+ "repo2": "Mengambil repository",
+ "repo3": "Menganalisis struktur"
+ },
+ "targetStep": {
+ "heading": "Ke mana Anda ingin deploy?",
+ "loadingSubtitle": "Memuat target deployment yang tersedia",
+ "loadingCheck": "Memeriksa server dan koneksi cloud...",
+ "deployAndBuildHeading": "Deploy & build",
+ "chooseSubtitle": "Pilih di mana aplikasi Anda akan berjalan",
+ "onlyOneSubtitle": "Hanya satu target deployment yang tersedia saat ini",
+ "noTargetSubtitle": "Belum ada target deployment yang tersedia",
+ "noTargetBody": "Hubungkan Openship Cloud atau tambahkan server untuk melanjutkan deployment ini.",
+ "addServer": "Tambahkan server Anda sendiri",
+ "continue": "Lanjutkan",
+ "chooseServer": "Pilih server",
+ "thisServerBadge": "Server Ini",
+ "thisServerHost": "Host saat ini — deployment berjalan di sini",
+ "searchPlaceholder": "Cari server…",
+ "noServersMatch": "Tidak ada server yang cocok dengan pencarian Anda.",
+ "requireCloudFeature": "Mendeploy ke Openship Cloud",
+ "saveDefault": "Simpan sebagai default saya untuk setiap deployment.",
+ "saveDefaultHint": "Ubah nanti di Pengaturan, atau per-deploy dari pemilih di halaman ini.",
+ "savedToastTitle": "Default",
+ "savedToast": "Disimpan sebagai target deployment default Anda",
+ "saveFailedToast": "Tidak dapat menyimpan default - deployment Anda tetap akan berlanjut",
+ "gitForwardLabel": "Teruskan kredensial git saya",
+ "gitForwardDescPre": "Clone di server menggunakan identitas ",
+ "gitForwardDescPost": " lokal Anda — tanpa build-and-upload. Tidak ada yang disimpan di server.",
+ "options": {
+ "serverViaSsh": "Deploy ke server remote Anda melalui SSH.",
+ "servers": "Server Anda",
+ "serversCount": "{count} server yang dikonfigurasi.",
+ "cloud": "Openship Cloud",
+ "cloudConnectedDesc": "Deploy ke infrastruktur cloud managed. Tidak perlu pengaturan server.",
+ "cloudDisconnectedDesc": "Hubungkan akun Openship Cloud Anda dan deploy ke infrastruktur managed."
+ },
+ "build": {
+ "heading": "Lokasi build",
+ "prepareHeading": "Siapkan lokasi",
+ "subtitle": "Di mana build berjalan",
+ "prepareSubtitle": "Di mana repo disiapkan",
+ "advanced": "Lanjutan",
+ "advancedSummary": "{action} di {location}",
+ "actionBuild": "Build",
+ "actionPrepare": "Siapkan",
+ "options": "Opsi",
+ "localLabel": "Mesin Ini",
+ "localDesc": "Build di sini, kirim output.",
+ "remoteLabel": "Remote",
+ "remoteDesc": "Build di target deployment.",
+ "cloudLabel": "Openship Cloud",
+ "cloudDesc": "Build cloud managed.",
+ "cloudLocalLabel": "Mesin Ini",
+ "cloudLocalDesc": "Build di sini, unggah output."
+ },
+ "hint": {
+ "buildTitle": "Kami akan build di tempat Anda deploy",
+ "prepareTitle": "Kami akan siapkan di tempat Anda deploy",
+ "cloud": "Build berjalan di infrastruktur cloud managed - tidak perlu pengaturan.",
+ "serverNamed": "Build berjalan di server Anda \"{name}\". Jika ini VPS kecil, build di mesin ini bisa jauh lebih cepat. Anda dapat mengubah ini di layar berikutnya atau menyimpan preferensi di Pengaturan.",
+ "server": "Build berjalan di server Anda. Jika ini VPS kecil, build di mesin ini bisa jauh lebih cepat. Anda dapat mengubah ini di layar berikutnya atau menyimpan preferensi di Pengaturan.",
+ "local": "Build berjalan di mesin ini. Anda dapat mengubah ini di layar berikutnya atau menyimpan preferensi di Pengaturan.",
+ "chooseBuildLocation": "Pilih lokasi build"
+ },
+ "clone": {
+ "heading": "Lokasi clone",
+ "descLead": "Build selalu berjalan di server. ",
+ "descDesktop": "Kredensial diteruskan sementara, tidak pernah disimpan.",
+ "descServer": "Menggunakan token sementara.",
+ "apiHostDesktopLabel": "Di perangkat Anda",
+ "apiHostServerLabel": "Di Openship",
+ "apiHostDesktopDesc": "Clone di sini, lalu unggah.",
+ "apiHostServerDesc": "Clone di Openship, lalu unggah.",
+ "serverLabel": "Di server",
+ "serverDesc": "Clone di server. Tidak perlu unggah."
+ },
+ "rollbackTitle": "Rollback & backup",
+ "rollbackHint": "Berapa banyak versi lama yang tetap dapat dipulihkan di server ini, dan apa yang dibackup sebelum deploy.",
+ "rollbackFirstDeploy": "Dapat diedit di pengaturan proyek setelah proyek ini pernah di-deploy.",
+ "backupSummaryNone": "Belum ada kebijakan backup",
+ "backupSummaryConfigured": "{count} kebijakan backup · pre-deploy: {preDeploy}",
+ "backupManage": "Kelola backup",
+ "on": "aktif",
+ "off": "nonaktif"
+ },
+ "summary": {
+ "buildAndDeploy": "Build & deploy:",
+ "build": "Build:",
+ "deploy": "Deploy:",
+ "static": "Statis",
+ "runtimeStatic": "Statis · dilayani edge",
+ "runtimeSandboxed": "Sandboxed",
+ "runtimeDirectWarning": "Tidak sandboxed",
+ "runtimeDirectHint": "Berjalan langsung di host — tidak sandboxed.",
+ "targetLocal": "Mesin Ini",
+ "targetServer": "Server Saya",
+ "targetCloud": "Openship Cloud",
+ "buildLocal": "Mesin Ini",
+ "buildRemote": "Remote",
+ "rollbackOne": "{count} versi",
+ "rollbackOther": "{count} versi",
+ "rollbackAuto": "Rollback: otomatis",
+ "rollbackSnapshotHint": "Salinan setiap versi disimpan, sehingga rollback tidak pernah rebuild. Ubah di bawah Lanjutan.",
+ "rollbackGitHint": "Berapa banyak versi lama yang tetap dapat dipulihkan. Ubah di bawah Lanjutan."
+ },
+ "power": {
+ "heading": "Kekuatan",
+ "subtitle": "Pilih ukuran runtime untuk Openship Cloud",
+ "ram": "RAM",
+ "disk": "Disk",
+ "vcpu": "vCPU",
+ "custom": "Kustom",
+ "customDesc": "Atur sendiri CPU, RAM, dan penyimpanan",
+ "tierMicroLabel": "Micro",
+ "tierMicroBestFor": "Proyek sampingan, lalu lintas rendah",
+ "tierLowLabel": "Rendah",
+ "tierLowBestFor": "Aplikasi kecil, API ringan",
+ "tierMediumLabel": "Sedang",
+ "tierMediumBestFor": "Sebagian besar aplikasi produksi",
+ "tierHighLabel": "Tinggi",
+ "tierHighBestFor": "Beban kerja berat, build cepat",
+ "modalTitle": "Runtime kustom",
+ "modalSubtitle": "Pilih CPU, RAM, dan disk untuk deployment ini.",
+ "vcpuField": "vCPU",
+ "ramField": "RAM (MB)",
+ "diskField": "Disk (GB)",
+ "cancel": "Batal",
+ "save": "Simpan"
+ },
+ "runtime": {
+ "heading": "Runtime",
+ "subtitle": "Bagaimana isolasi dilakukan",
+ "subtitleRam": "Bagaimana isolasi dilakukan · {ram} GB RAM",
+ "recommended": "Direkomendasikan",
+ "sandboxedLabel": "Sandboxed",
+ "sandboxedDesc": "Container terisolasi. Overhead kecil.",
+ "directLabel": "Langsung",
+ "directDesc": "Berjalan di host. Overhead paling rendah.",
+ "caveatLowRam": "RAM terbatas di sini — Sandboxed menambah ~50–80 MB. Direct lebih ringan, Sandboxed lebih aman.",
+ "caveat": "Artinya akses host penuh jika terkompromi. Sandboxed menambah ~1–2% CPU untuk isolasi nyata."
+ },
+ "serveMode": {
+ "heading": "Bagaimana cara penyajian",
+ "subtitle": "Sajikan file yang sudah dibangun, atau jalankan sebagai proses server aktif.",
+ "staticLabel": "Sajikan sebagai file statis",
+ "staticDesc": "Output build disajikan langsung oleh edge. Tidak ada yang berjalan sebagai proses.",
+ "serverLabel": "Jalankan sebagai proses server",
+ "serverDesc": "Mulai proses yang berjalan terus-menerus (membutuhkan perintah start dan port).",
+ "staticNote": "Dibangun di Docker sandbox terisolasi, lalu disajikan sebagai file statis oleh edge.",
+ "startCommandLabel": "Perintah start",
+ "startCommandPlaceholder": "npm start",
+ "startCommandHint": "Kosongkan untuk menyajikan output build sebagai file statis dari edge (tidak ada yang berjalan). Masukkan perintah untuk menjalankannya sebagai proses server aktif.",
+ "modeStatic": "Statis",
+ "modeProcess": "Proses server",
+ "portLabel": "Port"
+ },
+ "mail": {
+ "title": "Deploy webmail",
+ "subtitle": "Siapkan webmail self-hosted di domain pilihan Anda.",
+ "backToMail": "Kembali ke mail",
+ "missingServerTitle": "Server mail tidak ditemukan",
+ "missingServerBody": "Buka halaman ini dari ringkasan mail.",
+ "deployToTitle": "Deploy ke",
+ "deployToHint": "Webmail dapat berada di server mail ini, di host lain yang dikelola openship, atau di Opshcloud.",
+ "loadingTargets": "Memuat target…",
+ "notAvailable": "Tidak tersedia",
+ "domainTitle": "Domain",
+ "domainHintProxy": "Server mail Anda sudah memiliki hostname ini - kami akan memproxy-nya melalui VPS mail Anda ke workload Opshcloud. Tidak perlu perubahan DNS.",
+ "domainHintMailHost": "Hostname server mail Anda sendiri - sudah mengarah ke sini dan sudah memiliki sertifikat, jadi tidak ada yang perlu diatur. Gunakan webmail. jika Anda ingin memisahkannya.",
+ "domainHintCloud": "Arahkan CNAME ke URL *.opsh.io yang kami siapkan (akan terlihat setelah deploy).",
+ "domainHintDefault": "URL yang akan dikunjungi operator. DNS harus mengarah ke target deployment.",
+ "summary": "Ringkasan",
+ "summaryTarget": "Target",
+ "summaryDomain": "Domain",
+ "summaryMailServer": "Server mail",
+ "starting": "Memulai…",
+ "deployButton": "Deploy webmail",
+ "replaceButton": "Ganti webmail",
+ "legacyTitle": "Webmail ini mendahului aplikasi webmail",
+ "legacyBody": "Webmail ini di-deploy oleh Openship lama yang menyertakan webmail sebagai bundle alih-alih gambar, sehingga tidak dapat di-deploy ulang. Deploy sekarang akan menghapusnya - proyek, container, dan database sesinya - serta menginstal aplikasi webmail di tempatnya. Semua yang masuk ke dalamnya akan keluar, dan pengaturan webmail per pengguna akan hilang.",
+ "legacyKept": "Mailbox, data mail, dan setiap pesan tetap di server mail, tidak terpengaruh.",
+ "deployFailedTitle": "Deploy gagal",
+ "deployFailed": "Gagal memulai deploy"
+ }
+}
\ No newline at end of file
diff --git a/apps/dashboard/src/i18n/locales/id/deployments.json b/apps/dashboard/src/i18n/locales/id/deployments.json
new file mode 100644
index 000000000..ea09c95c8
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/id/deployments.json
@@ -0,0 +1,195 @@
+{
+ "time": {
+ "secondsAgo": "{n}d lalu",
+ "minutesAgo": "{n}m lalu",
+ "hoursAgo": "{n}j lalu",
+ "daysAgo": "{n}h lalu",
+ "monthsAgo": "{n}bl lalu",
+ "yearsAgo": "{n}th lalu"
+ },
+ "header": {
+ "title": "Deployment",
+ "loading": "Memuat…",
+ "countProjectOne": "{count} deployment",
+ "countProjectOther": "{count} deployment",
+ "countAllOne": "{deployments} total di {projects} proyek",
+ "countAllOther": "{deployments} total di {projects} proyek"
+ },
+ "filters": {
+ "searchPlaceholder": "Cari deployment...",
+ "all": "Semua",
+ "success": "Berhasil",
+ "failed": "Gagal",
+ "building": "Sedang Dibangun",
+ "canceled": "Dibatalkan"
+ },
+ "projectFilter": {
+ "allProjects": "Semua Proyek"
+ },
+ "sidebar": {
+ "overview": {
+ "title": "Ringkasan",
+ "total": "Total",
+ "successful": "Berhasil",
+ "failed": "Gagal",
+ "inProgress": "Sedang Berlangsung"
+ },
+ "autoDeploy": {
+ "title": "Auto-Deploy",
+ "description": "Aktifkan auto-deploy untuk memicu deployment secara otomatis setiap push ke branch utama Anda.",
+ "cta": "Buka pengaturan proyek"
+ },
+ "getStarted": {
+ "title": "Mulai",
+ "description": "Impor repository Git atau gunakan template untuk membuat deployment pertama Anda.",
+ "cta": "Deploy proyek"
+ },
+ "recent": {
+ "title": "Terbaru",
+ "empty": "Deployment Anda akan muncul di sini",
+ "unknown": "Tidak diketahui"
+ }
+ },
+ "stats": {
+ "total": "Total",
+ "projectCountOne": "{count} proyek",
+ "projectCountOther": "{count} proyek",
+ "success": "Berhasil",
+ "failed": "Gagal",
+ "inProgress": "Sedang Berlangsung",
+ "buildingNow": "sedang dibangun"
+ },
+ "status": {
+ "deployed": "Sudah Di-deploy",
+ "failed": "Gagal",
+ "canceled": "Dibatalkan",
+ "building": "Sedang Dibangun",
+ "deploying": "Sedang Di-deploy",
+ "partial": "Sebagian",
+ "actionRequired": "Tindakan diperlukan",
+ "rejected": "Ditolak",
+ "verifying": "Memverifikasi",
+ "noChanges": "Tidak ada perubahan",
+ "pending": "Tertunda"
+ },
+ "serviceStatus": {
+ "deployed": "Sudah Di-deploy",
+ "failed": "Gagal",
+ "cancelled": "Dibatalkan",
+ "skipped": "Dilewati",
+ "building": "Sedang Dibangun",
+ "deploying": "Sedang Di-deploy",
+ "running": "Berjalan",
+ "removedOnHost": "Dihapus di host",
+ "verifying": "Memverifikasi",
+ "pending": "Tertunda"
+ },
+ "card": {
+ "unknownProject": "Proyek Tidak Diketahui",
+ "versionTitle": "Versi {version}",
+ "active": "Aktif",
+ "activeTitle": "Ini adalah deployment yang sedang melayani proyek saat ini",
+ "pinned": "Disematkan",
+ "pinnedTitle": "Disematkan — dikecualikan dari pruning retensi. Tetap dapat dipulihkan secara rollback tanpa batas.",
+ "snapshotted": "Di-snapshot",
+ "snapshottedTitle": "Artifact diarsipkan — versi ini tersedia untuk rollback",
+ "manualDeploy": "Deploy manual",
+ "serviceTitle": "{name}: {label}",
+ "serviceTitleReason": "{name}: {label} ({reason})"
+ },
+ "empty": {
+ "filtered": {
+ "title": "Tidak ada deployment ditemukan",
+ "description": "Coba sesuaikan filter atau bersihkan pencarian Anda."
+ },
+ "none": {
+ "title": "Belum ada deployment",
+ "description": "Deploy proyek pertama Anda dan proyek akan muncul di sini dengan status build, detail commit, dan metrik performa.",
+ "deployButton": "Deploy Proyek",
+ "browseButton": "Jelajahi Template"
+ }
+ },
+ "modal": {
+ "title": "Detail Commit",
+ "project": "Proyek",
+ "unknownProject": "Proyek Tidak Diketahui",
+ "domain": "Domain",
+ "noDomain": "Tidak ada domain",
+ "status": "Status",
+ "environment": "Lingkungan",
+ "statusName": {
+ "success": "Berhasil",
+ "failed": "Gagal",
+ "building": "Sedang Dibangun",
+ "deploying": "Sedang Di-deploy",
+ "canceled": "Dibatalkan",
+ "pending": "Tertunda"
+ },
+ "changedFiles": "File yang Diubah ({count})",
+ "fileTypes": {
+ "added": "+ Ditambahkan",
+ "modified": "~ Diubah",
+ "removed": "- Dihapus"
+ },
+ "viewOnGithub": "Lihat di GitHub",
+ "visitSite": "Kunjungi Situs",
+ "manual": {
+ "title": "Deployment Manual",
+ "description": "Deployment ini dipicu secara manual dan tidak memiliki informasi commit.",
+ "deploymentId": "ID Deployment",
+ "created": "Dibuat",
+ "type": "Tipe",
+ "typeManual": "Manual"
+ }
+ },
+ "menu": {
+ "openDeployment": "Buka Deployment",
+ "viewRepository": "Lihat Repository",
+ "copyDomainUrl": "Salin URL Domain",
+ "copyBuildId": "Salin ID Build",
+ "cancelDeployment": "Batalkan Deployment",
+ "rollback": "Rollback ke versi ini",
+ "rollbackTitle": {
+ "enabled": "Pulihkan proyek ke versi ini (hanya kode — data tidak berubah)",
+ "active": "Ini sudah menjadi versi aktif",
+ "pruned": "Deployment ini tidak memiliki gambar yang tersimpan dan tidak ada commit untuk rebuild",
+ "notReady": "Hanya deployment yang sudah siap yang dapat di-rollback",
+ "rebuildOnly": "Pulihkan versi ini dengan rebuild commit-nya (gambarnya sudah di-prune)"
+ },
+ "pin": "Sematkan",
+ "unpin": "Lepas Sematan",
+ "pinTitle": {
+ "pin": "Pertahankan deployment ini agar tetap dapat di-rollback tanpa batas (kapasitas: 10 disematkan per proyek)",
+ "unpin": "Izinkan deployment ini di-prune ketika retensi meluap",
+ "pruned": "Tidak dapat disematkan — artifact sudah di-prune"
+ },
+ "deleteDeployment": "Hapus Deployment",
+ "rollbackFailed": "Rollback gagal",
+ "pinFailed": "Gagal menyematkan deployment",
+ "unpinFailed": "Gagal melepas sematan deployment",
+ "rollbackModeInstant": "Pulihkan instan — menggunakan kembali gambar versi ini yang tersimpan, tanpa rebuild.",
+ "rollbackModeRebuild": "Rebuild — gambar versi ini tidak lagi ada di host, sehingga commit-nya dibangun ulang.",
+ "rollbackModeReacquireImage": "Pulihkan dari registry — mengambil referensi gambar yang tercatat untuk rilis ini; tanpa rebuild sumber.",
+ "rollbackModeMixed": "Sebagian besar instan — {count} layanan kehilangan gambarnya dan akan di-rebuild."
+ },
+ "rollbackConfirm": {
+ "title": "Rollback ke versi ini?",
+ "dataUnchanged": "Ini hanya memulihkan kode aplikasi. Data database dan volume tidak berubah.",
+ "envHeading": "Variabel lingkungan",
+ "envSummaryOverlay": "{count} variabel berubah — nilai rilis ini ditimpa di atas yang saat ini.",
+ "envSummaryReplace": "{count} variabel berubah — lingkungan rilis ini digunakan persis seperti yang ditangkap.",
+ "envNoChanges": "Tidak ada variabel lingkungan yang berubah.",
+ "envUnchanged": "Container yang ada di-restart alih-alih dibuat ulang, sehingga lingkungannya tidak tersentuh.",
+ "envNoValues": "Nilai tidak pernah ditampilkan di sini.",
+ "envTruncated": "Menampilkan {shown} dari {total}.",
+ "dirFrozenWins": "kembali ke nilai rilis ini",
+ "dirRemovedSince": "kembali — dihapus sejak rilis ini",
+ "dirAddedSinceKept": "dipertahankan apa adanya — ditambahkan sejak rilis ini",
+ "dirAddedSinceDropped": "dihapus — ditambahkan sejak rilis ini",
+ "scopeAmbiguous": "ditangkap tanpa cakupan layanan, sehingga satu nilai berlaku untuk setiap layanan",
+ "untouchedHeading": "Layanan yang ditambahkan sejak rilis ini",
+ "untouchedNote": "Layanan ini tetap berjalan dan tidak tersentuh: {names}",
+ "confirm": "Rollback",
+ "cancel": "Batal"
+ }
+}
\ No newline at end of file
diff --git a/apps/dashboard/src/i18n/locales/id/emails.json b/apps/dashboard/src/i18n/locales/id/emails.json
new file mode 100644
index 000000000..8c9dd3271
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/id/emails.json
@@ -0,0 +1,200 @@
+{
+ "page": {
+ "title": "Server Email",
+ "subtitle": "Siapkan server mail self-hosted hanya dengan beberapa klik",
+ "backToServers": "Kembali ke server mail",
+ "backToAdmin": "Kembali ke admin mail",
+ "addServer": "Tambah server mail",
+ "removeModal": {
+ "title": "Hapus server mail ini?",
+ "description": "Hapus {label} dari daftar mail saja. Stack mail tetap berjalan dan tidak ada yang di-uninstall — tambahkan kembali kapan saja dengan \"Adopt\" untuk instalasi yang sudah ada.",
+ "submit": "Hapus dari daftar",
+ "submitting": "Menghapus…",
+ "body": "Karena instalasi di server dibiarkan utuh, mengadopsi ulang nanti akan memulihkan manajemen penuh tanpa reinstalasi."
+ },
+ "errors": {
+ "resetFailed": "Reset gagal",
+ "ackDns": "Tidak dapat mengonfirmasi DNS",
+ "ackPtr": "Tidak dapat mengonfirmasi PTR",
+ "selectServer": "Pilih server terlebih dahulu",
+ "resolutionFailed": "Resolusi gagal"
+ }
+ },
+ "adopt": {
+ "title": "Adopsi server mail yang sudah ada",
+ "intro": "Sudah menyiapkan server mail dengan Openship di salah satu server Anda? Pilih dan pindai — jika stack mail ditemukan, stack akan diadopsi ulang ke dashboard tanpa reinstalasi atau mengubah data Anda.",
+ "scanning": "Memindai…",
+ "scan": "Pindai server",
+ "detected": "Server mail terdeteksi",
+ "domainLabel": "Domain:",
+ "unknown": "tidak diketahui",
+ "stats": "iRedMail berjalan: {iredmail} · Instalasi: {install} · Webmail: {webmail}",
+ "yes": "ya",
+ "no": "tidak",
+ "complete": "lengkap",
+ "inProgress": "sedang berlangsung",
+ "present": "ada",
+ "notDeployed": "belum di-deploy",
+ "cancel": "Batal",
+ "adopt": "Adopt",
+ "noInstall": "Tidak ditemukan instalasi server mail di server ini.",
+ "scanFailed": "Pindai gagal — apakah server dapat dijangkau?",
+ "adoptFailed": "Adopt gagal"
+ },
+ "dns": {
+ "heading": "Publikasikan record DNS sebelum melanjutkan",
+ "bodyBefore": "Pengiriman mail akan terganggu tanpa record ini. Tambahkan di penyedia DNS Anda, lalu klik ",
+ "action": "Saya sudah mengatur record - lanjutkan",
+ "bodyAfterResume": " di bawah. Instalasi dilanjutkan dari langkah {resumeStep} (sertifikat SSL).",
+ "bodyAfter": " di bawah.",
+ "autoConfigure": "Konfigurasi otomatis DNS",
+ "autoConfigureDesc": "Pilih penyedia yang menghosting DNS domain Anda. Kami akan mendorong record untuk Anda menggunakan API mereka.",
+ "otherManual": "Lainnya / atur secara manual",
+ "otherManualDesc": "Salin setiap record dari banner dan tempel ke UI penyedia Anda",
+ "pickAnother": "Pilih penyedia lain",
+ "comingSoon": "Integrasi {provider} - segera hadir",
+ "comingSoonBefore": "Integrasi API langsung dengan penyedia ini belum tersedia. Untuk saat ini, salin setiap record dari banner dan tempel ke UI penyedia DNS Anda. Setelah selesai, klik ",
+ "comingSoonAfter": ".",
+ "gotIt": "Mengerti",
+ "providers": {
+ "cloudflare": "DNS gratis, propagasi cepat. Pilihan paling umum.",
+ "hostinger": "Jika Anda membeli domain melalui Hostinger.",
+ "route53": "Zona DNS yang di-host AWS.",
+ "digitalocean": "Zona yang dikelola DigitalOcean.",
+ "namecheap": "DNS default Namecheap.",
+ "google": "Zona yang dikelola Google."
+ }
+ },
+ "recordCard": {
+ "priority": "Prioritas: {priority}",
+ "copyValue": "Salin nilai"
+ },
+ "progress": {
+ "liveLogs": "Log langsung",
+ "lineOne": "{count} baris",
+ "lineOther": "{count} baris",
+ "cancel": "Batal",
+ "resetTitle": "Hapus status instalasi di server dan mulai dari awal",
+ "confirmReset": "Klik lagi untuk mengonfirmasi",
+ "reset": "Reset",
+ "waiting": "Menunggu output…",
+ "noBuffered": "Tidak ada output yang di-buffer untuk menjalankan ini.",
+ "willStream": "Log akan ditampilkan di sini setelah pengaturan dimulai.",
+ "jumpToLatest": "Lompat ke yang terbaru",
+ "setupFailed": "Pengaturan gagal",
+ "retryFromStep": "Coba ulang dari langkah {resumeStep}",
+ "resumePausedTitle": "Pengaturan dijeda",
+ "stepLabel": "Langkah {step}",
+ "continueFromStep": "Lanjutkan dari langkah {step}"
+ },
+ "serverList": {
+ "title": "Server mail",
+ "addServer": "Tambah server mail",
+ "removeFromList": "Hapus dari daftar",
+ "removeAria": "Hapus {name} dari daftar mail",
+ "installing": "Menginstal",
+ "running": "Berjalan",
+ "incomplete": "Tidak Lengkap",
+ "stoppedAt": "Berhenti · langkah {step}",
+ "open": "Buka",
+ "openAria": "Buka {name}",
+ "actionsAria": "Tindakan untuk {name}",
+ "overviewTitle": "Ringkasan mail",
+ "hint": "Menghapus server hanya melepaskannya dari daftar ini — instalasi tetap berjalan dan dapat diadopsi ulang."
+ },
+ "setup": {
+ "title": "Pengaturan Server Mail",
+ "subtitle": "Deploy stack mail self-hosted yang lengkap di server ini",
+ "domainLabel": "Domain",
+ "domainPlaceholder": "example.com",
+ "willBeAtBefore": "Server mail Anda akan berada di ",
+ "adminPasswordLabel": "Kata Sandi Admin",
+ "passwordPlaceholder": "Kata sandi kuat - atau klik Generate",
+ "passwordHint": "Minimal 12 karakter. Digunakan untuk masuk ke mailbox setelah pengaturan — ubah nanti dari panel admin di bawah.",
+ "relay": {
+ "toggle": "Relay outbound melalui penyedia email",
+ "hint": "Direkomendasikan. Kirim melalui IP tepercaya penyedia — IP server baru sering masuk daftar blokir, dan banyak host memblokir port 25 outbound. Penerimaan tetap di server ini.",
+ "region": "Wilayah",
+ "username": "Nama pengguna SMTP",
+ "password": "Kata sandi SMTP",
+ "perDomainNote": "Berlaku untuk semua domain. Routing per domain dan record DKIM / MAIL FROM per domain dikonfigurasi setelah instalasi di tab Mengirim."
+ },
+ "startSetup": "Mulai Pengaturan",
+ "startFailed": "Pengaturan tidak dapat dimulai",
+ "startFailedFallback": "Pengaturan tidak dapat dimulai, dan server tidak memberikan alasan. Periksa log API.",
+ "adoptCta": "Sudah memiliki server mail? Adopsi",
+ "whatInstalled": "Apa yang diinstal",
+ "features": {
+ "stackLabel": "Stack server mail",
+ "stackDesc": "Postfix, Dovecot, anti-spam",
+ "sslLabel": "Sertifikat SSL",
+ "sslDesc": "Let's Encrypt auto-SSL",
+ "dnsLabel": "Konfigurasi DNS",
+ "dnsDesc": "Record DKIM, SPF, DMARC",
+ "adminLabel": "Panel Admin",
+ "adminDesc": "Kelola mailbox, domain, dan alias"
+ },
+ "prerequisites": "Prasyarat",
+ "prereq1": "Domain dengan akses DNS",
+ "prereq2": "Port 25 tidak diblokir oleh penyedia Anda",
+ "prereq2Relay": "Port 25 terbuka untuk mail masuk (outbound melewati relay Anda)",
+ "prereq3": "Server Ubuntu/Debian bersih (direkomendasikan)",
+ "hide": "Sembunyikan",
+ "reveal": "Tampilkan",
+ "generateTitle": "Buat kata sandi acak yang kuat",
+ "generate": "Buat"
+ },
+ "sidebar": {
+ "portConflict": {
+ "title": "Konflik Port Terdeteksi",
+ "subtitle": "Port 80/443 dibutuhkan untuk server mail",
+ "managed": "Managed",
+ "known": "Layanan yang Diketahui",
+ "unknown": "Tidak Diketahui",
+ "container": "Container: {name}",
+ "killWarning": "Pastikan proses ini tidak diperlukan sebelum menghentikannya. Tindakan ini tidak dapat dibatalkan.",
+ "allResolved": "Semua konflik terselesaikan",
+ "rerun": "Jalankan ulang pemeriksaan port dan lanjutkan pengaturan.",
+ "resumeStep3": "Lanjutkan dari Langkah 3"
+ },
+ "completion": {
+ "ready": "Server Mail Siap!",
+ "webmail": "Webmail",
+ "adminPanel": "Panel Admin"
+ },
+ "dnsRef": {
+ "title": "Record DNS yang Diperlukan",
+ "subtitle": "Referensi - ketuk untuk melihat record yang Anda publikasikan"
+ },
+ "steps": {
+ "title": "Semua langkah",
+ "interrupted": "Instalasi terganggu sebelum langkah {step}. Lanjutkan untuk melanjutkan dari tempat terakhir.",
+ "resume": "Lanjutkan",
+ "retry": "Coba Ulang"
+ },
+ "details": {
+ "title": "Detail Pengaturan",
+ "domain": "Domain",
+ "mailServer": "Server Mail",
+ "started": "Dimulai"
+ }
+ },
+ "ptr": {
+ "heading": "Atur reverse DNS (PTR) sebelum melanjutkan",
+ "bodyBefore": "PTR dikonfigurasi di panel ",
+ "bodyStrongPanel": "penyedia VPS Anda",
+ "bodyMid": " (Hostinger, DigitalOcean, AWS, dll.), ",
+ "bodyStrongNot": "bukan",
+ "bodyAfter": " di penyedia DNS Anda. Tanpa rDNS yang cocok, Gmail dan Outlook akan menolak mail outbound Anda. Instalasi dilanjutkan dari langkah {resumeStep} (SSL).",
+ "recommended": "direkomendasikan",
+ "ipLabel": "IP",
+ "setTo": "Atur ke",
+ "copy": "Salin",
+ "howTo": "Cara mengaturnya:",
+ "howToBody": " masuk ke panel penyedia VPS Anda, temukan pengaturan jaringan / DNS / rDNS server, dan atur record PTR untuk setiap IP ke ",
+ "howToAfter": ". Propagasi biasanya memakan waktu 5–15 menit.",
+ "verifyWith": "Verifikasi dengan:",
+ "shouldReturn": " seharusnya mengembalikan ",
+ "action": "Saya sudah mengatur PTR - lanjutkan"
+ }
+}
\ No newline at end of file
diff --git a/apps/dashboard/src/i18n/locales/id/emailsAdmin.json b/apps/dashboard/src/i18n/locales/id/emailsAdmin.json
new file mode 100644
index 000000000..e48df19d0
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/id/emailsAdmin.json
@@ -0,0 +1,743 @@
+{
+ "panel": {
+ "tabs": {
+ "overview": "Ringkasan",
+ "domains": "Domain",
+ "mailboxes": "Mailbox",
+ "aliases": "Alias",
+ "notifications": "Notifikasi",
+ "dns": "DNS",
+ "health": "Kesehatan",
+ "backup": "Backup",
+ "sending": "Mengirim",
+ "advanced": "Lanjutan"
+ },
+ "ariaLabel": "Bagian admin mail"
+ },
+ "notifications": {
+ "heading": "Aturan notifikasi",
+ "description": "Dapatkan pemberitahuan di saluran notifikasi ketika mail tiba di alamat di server ini.",
+ "newRule": "Aturan baru",
+ "newTitle": "Aturan notifikasi baru",
+ "editTitle": "Edit aturan notifikasi",
+ "edit": "Edit",
+ "emptyTitle": "Belum ada aturan notifikasi",
+ "emptyBody": "Tambahkan aturan untuk mendapatkan pemberitahuan di Slack, Telegram, Discord, atau webhook ketika mail tiba di mailbox atau domain di server ini.",
+ "noChannels": "Anda belum memiliki saluran notifikasi yang terverifikasi. Tambahkan dan verifikasi di Pengaturan → Notifikasi, lalu kembali untuk membuat aturan.",
+ "addChannel": "Tambah saluran notifikasi",
+ "manageChannels": "Kelola saluran",
+ "fieldName": "Nama",
+ "namePlaceholder": "Kotak masuk support → #support",
+ "fieldScope": "Pantau",
+ "scopeMailbox": "Satu mailbox",
+ "scopeDomain": "Satu domain",
+ "scopeAll": "Setiap domain di server ini",
+ "scopeAllSummary": "Setiap domain",
+ "fieldAddress": "Alamat",
+ "fieldDomain": "Domain",
+ "mailboxCaveat": "Cocokkan dari header To dan Cc, sehingga mail yang mencapai mailbox ini melalui Bcc atau alias tidak akan cocok. Pantau seluruh domain jika Anda memerlukan setiap pesan.",
+ "fieldFrom": "From mengandung",
+ "fieldSubject": "Subject mengandung",
+ "patternPlaceholder": "opsional",
+ "patternHint": "Tidak peka huruf besar-kecil. Gunakan * sebagai wildcard — sisanya dicocokkan secara literal.",
+ "fieldChannels": "Beritahu saluran ini",
+ "fieldEnabled": "Aturan aktif",
+ "channelCount": "{count} saluran",
+ "colRule": "Aturan",
+ "colWatch": "Memantau",
+ "colChannels": "Saluran",
+ "colStatus": "Status",
+ "active": "Aktif",
+ "disabled": "Nonaktif",
+ "paused": "Dijeda",
+ "deleteTitle": "Hapus aturan notifikasi",
+ "deleteConfirm": "Hapus aturan",
+ "deleting": "Menghapus…",
+ "deleteHint": "Notifikasi yang ada tidak terpengaruh. Mail yang sudah ditangkap tetapi belum diproses akan dibuang.",
+ "save": "Simpan aturan",
+ "saving": "Menyimpan…",
+ "cancel": "Batal",
+ "test": "Uji sekarang",
+ "testing": "Menguji…",
+ "testResult": "Membaca {read} pesan yang ditangkap: {matched} cocok dengan aturan, {dropped} difilter. Tidak ada yang dikirim atau dihapus.",
+ "testFailed": "Tidak dapat menjalankan uji.",
+ "loadFailed": "Tidak dapat memuat aturan notifikasi.",
+ "saveFailed": "Tidak dapat menyimpan aturan.",
+ "deleteFailed": "Tidak dapat menghapus aturan.",
+ "confirmDelete": "Hapus aturan \"{name}\"? Penangkapan berhenti untuk alamatnya kecuali aturan lain masih membutuhkannya."
+ },
+ "sending": {
+ "title": "Pengiriman outbound",
+ "subtitle": "Penerimaan tetap di server ini. Pilih bagaimana mail outbound keluar.",
+ "modeDirectTitle": "Pengiriman langsung",
+ "modeDirectDesc": "Server ini mengirim mail outbound sendiri. Sederhana, tetapi IP VPS baru sering masuk daftar blokir dan banyak host memblokir port 25 outbound — mail mungkin masuk spam atau gagal.",
+ "modeRelayTitle": "Relay melalui penyedia",
+ "modeRelayDesc": "Serahkan mail outbound ke smarthost — SES, SendGrid, Mailgun, Postmark, Resend, atau server SMTP apa pun. Pengiriman terbaik. Kotak masuk dan semua DNS penerimaan tetap seperti apa adanya.",
+ "pathTitle": "Jalur outbound",
+ "setUpRelay": "Siapkan relay",
+ "directActive": "Mail outbound keluar dari server ini secara langsung. Tidak perlu konfigurasi — pilih relay di atas jika pengiriman menjadi masalah.",
+ "switchToDirect": "Beralih ke pengiriman langsung",
+ "switchToDirectHint": "Menghapus smarthost dan kredensial yang tersimpan, serta menghapus include SPF relay dari DNS Anda. Mail outbound langsung ke server penerima masing-masing.",
+ "statusOn": "Relay aktif",
+ "statusOff": "Pengiriman langsung (default)",
+ "sendersTitle": "Siapa yang di-relay?",
+ "scopeAll": "Semua pengirim",
+ "scopeSelected": "Pengirim yang dipilih",
+ "scopeAllHint": "Setiap domain di server ini mengirim melalui relay.",
+ "scopeSelectedHint": "Hanya pengirim yang Anda pilih yang di-relay; sisanya tetap mengirim langsung.",
+ "sendersDomains": "Domain utuh",
+ "sendersAddresses": "Pengirim individu",
+ "sendersAddressesHint": "Relay satu mailbox tanpa me-relay seluruh domainnya — contact@example.com dapat mengirim melalui penyedia Anda sambil tetap menerima melalui IMAP di sini.",
+ "senderPlaceholder": "contact@example.com",
+ "addSender": "Tambah",
+ "removeSender": "Hapus",
+ "senderInvalid": "Masukkan satu alamat biasa seperti contact@example.com — tanpa nama tampilan.",
+ "noSenders": "Pilih setidaknya satu domain atau alamat pengirim untuk di-relay.",
+ "noDomains": "Belum ada domain yang ditemukan di server ini.",
+ "credentialsTitle": "Penyedia dan kredensial",
+ "provider": "Penyedia",
+ "regionFor": "Wilayah {provider}",
+ "host": "Host SMTP",
+ "port": "Port",
+ "username": "Nama pengguna SMTP",
+ "password": "Kata sandi SMTP",
+ "passwordKeep": "Kosongkan untuk mempertahankan kata sandi saat ini",
+ "port465Blocked": "Port 465 (TLS implisit) adalah pengaturan server-wide, sehingga tidak dapat digunakan saat hanya beberapa pengirim yang di-relay. Gunakan port STARTTLS penyedia — biasanya 587.",
+ "spfIncludeLabel": "Include SPF",
+ "spfIncludeHint": "Tempel mekanisme include: yang disuruh dipublikasikan oleh penyedia Anda. Dibiarkan kosong kami tidak akan menyentuh record SPF Anda — tetapi mail yang di-relay melalui host yang tidak terdaftar di SPF akan gagal SPF.",
+ "spfNone": "Tidak ada",
+ "mailFromDomain": "Domain MAIL FROM kustom",
+ "mailFromHint": "Opsional. Subdomain seperti bounce.example.com untuk penyelarasan SPF/bounce. Konfigurasikan juga di penyedia.",
+ "identityTitle": "Identitas {provider} per domain",
+ "identityHint": "{provider} memverifikasi setiap domain pengirim secara terpisah. Tempel record setiap domain yang di-relay (opsional — amavis sudah menandatangani, sehingga DMARC selaras tanpa mereka, tetapi penyedia membutuhkan domain terverifikasi sebelum mengirim).",
+ "identityPrimary": "Primer",
+ "dkimRecords": "Record DKIM {provider}",
+ "dkimRecordsHint": "Tempel record CNAME yang ditampilkan penyedia Anda (SES: console → Verified identities → domain Anda → DKIM). Kami render + verifikasi di tab DNS.",
+ "dkimName": "Nama CNAME",
+ "dkimValue": "Nilai CNAME",
+ "summaryTitle": "Ringkasan",
+ "summaryTarget": "Smarthost",
+ "summarySenders": "Pengirim",
+ "summaryUpdated": "Terakhir disimpan",
+ "guidanceTitle": "Sebelum ini berfungsi",
+ "guidanceVerify": "Verifikasi domain Anda di Amazon SES console.",
+ "guidanceSandbox": "Minta akses produksi — SES dimulai di sandbox yang hanya mengirim ke alamat terverifikasi.",
+ "guidanceCreds": "Gunakan kredensial SMTP SES (pengguna SMTP IAM) — bukan kunci akses AWS Anda.",
+ "guidanceDns": "Publikasikan record DNS di tab DNS, lalu pindai ulang untuk mengonfirmasi.",
+ "guidanceSystem": "Mail sistem (reset kata sandi, undangan, notifikasi) juga dikirim melalui relay ini — kecuali Anda telah mengatur pengirim terpisah di Pengaturan → Email.",
+ "testLiveNote": "Mengirim melalui jalur yang aktif sekarang, bukan perubahan yang belum disimpan di atas.",
+ "save": "Aktifkan relay",
+ "update": "Simpan perubahan",
+ "disable": "Nonaktifkan relay",
+ "saved": "Relay outbound diperbarui.",
+ "disabled": "Relay dinonaktifkan — kembali ke pengiriman langsung.",
+ "saveFailed": "Tidak dapat memperbarui relay.",
+ "disableFailed": "Tidak dapat menonaktifkan relay.",
+ "dnsHint": "Record send-hop penyedia sekarang muncul di tab DNS. Publikasikan + verifikasi untuk pengiriman penuh."
+ },
+ "overview": {
+ "mailServer": "Server mail",
+ "protocolDetails": "Detail protokol →",
+ "hostname": "Hostname",
+ "webmailAt": "Webmail di",
+ "openWebmail": "Buka webmail",
+ "deployWebmail": "Deploy webmail",
+ "legacyWebmail": "Di-deploy oleh Openship lama — dapat diganti dengan aplikasi webmail, yang diperbarui bersama rilis Anda.",
+ "upgradeWebmail": "Ganti webmail",
+ "setupGuides": "Panduan pengaturan",
+ "setupGuidesDesc": "Panduan langkah demi langkah untuk setiap cara umum menggunakan mailbox ini - dari ponsel Anda hingga codebase. Membuka di tab baru.",
+ "guides": {
+ "iosTitle": "Mail iOS & macOS",
+ "iosSubtitle": "Tambah sebagai IMAP di iPhone / iPad / Mac",
+ "androidTitle": "Aplikasi Gmail Android",
+ "androidSubtitle": "Tambah sebagai akun IMAP pihak ketiga",
+ "desktopTitle": "Klien desktop",
+ "desktopSubtitle": "Thunderbird, Outlook, Spark, K-9",
+ "codeTitle": "Kirim melalui kode",
+ "codeSubtitle": "Node.js, Python, di mana pun SMTP berfungsi"
+ },
+ "mailStats": "Statistik mail",
+ "statsFailed": "Statistik gagal",
+ "domains": "Domain",
+ "mailboxes": "Mailbox",
+ "aliases": "Alias",
+ "storage": "Penyimpanan",
+ "messages": "Pesan",
+ "totalSuffix": "{count} total",
+ "quickActions": "Aksi cepat",
+ "addMailbox": "Tambah mailbox",
+ "addDomain": "Tambah domain",
+ "reviewDns": "Tinjau record DNS"
+ },
+ "webmail": {
+ "title": "Webmail",
+ "subtitle": "Klien mail yang digunakan pengguna Anda untuk masuk.",
+ "opening": "Membuka proyek webmail…",
+ "noServerTitle": "Belum ada server mail",
+ "noServerBody": "Webmail hanya klien — membutuhkan server mail untuk masuk. Siapkan satu terlebih dahulu, lalu kembali ke sini untuk menginstalnya.",
+ "installTitle": "Webmail belum diinstal",
+ "installBody": "Deploy klien webmail agar mailbox Anda dapat dibaca di browser. Diinstal sebagai proyek openship biasa, sehingga Anda mengelolanya di bawah Proyek seperti apa pun yang Anda deploy.",
+ "adoptTitle": "Webmail belum dikelola di sini",
+ "chooseTitle": "Server mail mana?",
+ "chooseBody": "Setiap server mail memiliki webmail sendiri. Pilih domain yang ingin Anda buka.",
+ "incompleteTitle": "Server mail belum siap",
+ "incompleteBody": "{server} masih dalam pengaturan. Webmail masuk ke dalamnya, jadi selesaikan server mail terlebih dahulu — lalu instal webmail dari sini.",
+ "incompleteAction": "Selesaikan pengaturan mail",
+ "runningNote": "Sebuah webmail sudah berjalan di {hostname} tetapi tidak ada yang mengelolanya di sini. Menginstal mengadopsinya ke dalam proyek alih-alih membangun yang kedua."
+ },
+ "domains": {
+ "heading": "Domain",
+ "description": "Setiap baris sesuai dengan baris di vmail.domain di VPS mail. Domain tambahan menerima mail begitu record MX-nya mengarah ke sini.",
+ "addDomain": "Tambah domain",
+ "editAction": "Edit",
+ "deleteAction": "Hapus",
+ "colDomain": "Domain",
+ "colMailboxes": "Mailbox",
+ "colAliases": "Alias",
+ "colQuota": "Quota default",
+ "colStatus": "Status",
+ "active": "Aktif",
+ "disabled": "Nonaktif",
+ "primary": "Primer",
+ "pendingTitle": "Publikasikan record DNS untuk {domain}",
+ "pendingDescBefore": "Mail dapat mengalir ke ",
+ "pendingDescAfter": " begitu record ini live. Record MX mengarah kembali ke server mail Anda yang ada; DKIM dihasilkan secara otomatis saat domain ditambahkan. Tambahkan di penyedia DNS Anda, lalu klik ",
+ "pendingDescAction": "Saya sudah mengatur record - lanjutkan",
+ "pendingDescEnd": ".",
+ "emptyTitle": "Belum ada domain",
+ "emptyDesc": "Tambah domain untuk mulai menghosting mailbox di dalamnya. Pastikan record MX-nya mengarah ke server mail ini.",
+ "acknowledgeFailed": "Gagal mengonfirmasi record DNS",
+ "loadFailed": "Gagal memuat domain",
+ "create": {
+ "title": "Tambah domain",
+ "description": "Menambahkan entri baru ke vmail.domain di VPS mail. Pastikan record MX untuk domain ini mengarah ke server mail sebelum mengirim mail melaluinya.",
+ "submit": "Buat domain",
+ "submitting": "Membuat…",
+ "domainLabel": "Domain",
+ "domainHint": "mis. acme.com - tanpa protokol, tanpa path.",
+ "descLabel": "Deskripsi",
+ "descHint": "Opsional - hanya ditampilkan di dashboard.",
+ "descPlaceholder": "Pemasaran Acme Corp",
+ "quotaLabel": "Quota mailbox default (GB)",
+ "quotaHint": "Opsional. Default untuk mailbox baru di domain ini; dapat di-override per mailbox. Kosongkan untuk tidak terbatas."
+ },
+ "editForm": {
+ "title": "Edit {domain}",
+ "submit": "Simpan perubahan",
+ "submitting": "Menyimpan…",
+ "descLabel": "Deskripsi",
+ "quotaLabel": "Quota mailbox default (GB)",
+ "quotaHint": "Kosongkan atau 0 untuk tidak terbatas.",
+ "activeLabel": "Aktif",
+ "activeDesc": "Ketika tidak dicentang, mail yang ditujukan ke domain ini ditolak di gerbang MX."
+ },
+ "deleteForm": {
+ "title": "Hapus {domain}?",
+ "descDependents": "Domain ini masih memiliki {parts}. Centang kotak di bawah untuk menghapusnya juga - file mailbox di disk akan dihapus dan tidak dapat dibatalkan.",
+ "descSimple": "Menghapus baris domain dan pemetaan admin. Mail ke domain ini akan mulai ditolak segera.",
+ "submitWithParts": "Hapus domain + {parts}",
+ "submit": "Hapus domain",
+ "submitting": "Menghapus…",
+ "alsoDelete": "Hapus juga {parts}",
+ "alsoDeleteDesc": "Menghapus secara permanen setiap mailbox di bawah domain ini (baris DB dan file Maildir di disk) beserta semua alias. Tidak dapat dibatalkan.",
+ "errorDependents": "Domain masih memiliki {mailboxes} mailbox dan {aliases} alias. Centang \"Hapus juga…\" untuk menghapusnya, atau hapus secara manual terlebih dahulu.",
+ "mailboxOne": "{count} mailbox",
+ "mailboxOther": "{count} mailbox",
+ "aliasOne": "{count} alias",
+ "aliasOther": "{count} alias",
+ "partsJoin": " dan "
+ }
+ },
+ "dns": {
+ "heading": "Record DNS",
+ "description": "Record yang dibutuhkan setiap domain di penyedia DNS Anda untuk pengiriman mail. Pilih domain untuk melihat recordnya dan verifikasi apakah sudah dipublikasikan.",
+ "domainLabel": "Domain",
+ "loading": "Memuat…",
+ "primary": "primer",
+ "recordsTitle": "Record untuk dipublikasikan",
+ "recordsDesc": "Publikasikan ini di penyedia DNS Anda untuk {domain}.",
+ "recordsEmpty": "Wizard instalasi membuat record selama langkah DKIM; domain tambahan mendapatkannya saat ditambahkan.",
+ "noRecordsFor": "Tidak ada record DNS untuk {domain}.",
+ "checkTitle": "Pemeriksaan record yang dipublikasikan",
+ "checkDesc": "Pencarian DNS publik langsung untuk {domain}, dibandingkan dengan record di atas.",
+ "rescan": "Pindai ulang",
+ "verify": "Verifikasi",
+ "runLookupBefore": "Jalankan pencarian DNS langsung untuk ",
+ "runLookupAfter": " dan bandingkan dengan record di atas.",
+ "noRecordsVerify": "Tidak ada record untuk {domain} yang dapat diverifikasi.",
+ "scanFailed": "Pindai DNS gagal",
+ "expected": "Diharapkan",
+ "actual": "Aktual",
+ "noRecord": "(tidak ada record)",
+ "pass": "Lolos",
+ "warning": "Peringatan",
+ "fail": "Gagal",
+ "unknown": "Tidak Diketahui"
+ },
+ "mailboxes": {
+ "heading": "Mailbox",
+ "description": "Setiap baris adalah akun IMAP/SMTP di server mail. Kata sandi di-hash dengan doveadm SSHA512 sebelum disimpan.",
+ "addMailbox": "Tambah mailbox",
+ "domainLabel": "Domain",
+ "loading": "Memuat…",
+ "activeCount": "{active} dari {total} aktif",
+ "colMailbox": "Mailbox",
+ "colQuota": "Quota",
+ "colStatus": "Status",
+ "unlimited": "Tidak Terbatas",
+ "active": "Aktif",
+ "disabled": "Nonaktif",
+ "postmaster": "Postmaster",
+ "editAction": "Edit",
+ "deleteAction": "Hapus",
+ "loadDomainsFailed": "Gagal memuat domain",
+ "loadFailed": "Gagal memuat mailbox",
+ "emptyTitle": "Belum ada mailbox",
+ "emptyDesc": "Buat mailbox pertama untuk {domain}. Kata sandi di-hash dengan doveadm SSHA512 - hanya hash yang disimpan di disk.",
+ "create": {
+ "title": "Mailbox baru · {domain}",
+ "description": "Membuat akun IMAP/SMTP di server mail. Kata sandi di-hash dengan doveadm SSHA512 di VPS - hanya hash yang disimpan.",
+ "submit": "Buat mailbox",
+ "submitting": "Membuat…",
+ "localPartLabel": "Bagian lokal",
+ "localPartHint": "Bagian sebelum @. Huruf kecil, alfanumerik.",
+ "fullNameLabel": "Nama lengkap",
+ "fullNameHint": "Nama tampilan yang ditampilkan di klien mail. Opsional.",
+ "passwordLabel": "Kata sandi",
+ "passwordHint": "Minimal 8 karakter. Atau buat yang baru.",
+ "generate": "Buat",
+ "quotaLabel": "Quota (GB)",
+ "quotaHint": "0 atau kosong = tidak terbatas.",
+ "hide": "Sembunyikan",
+ "show": "Tampilkan",
+ "copy": "Salin",
+ "copyPassword": "Salin kata sandi"
+ },
+ "editForm": {
+ "title": "Edit {username}",
+ "submit": "Simpan perubahan",
+ "submitting": "Menyimpan…",
+ "fullNameLabel": "Nama lengkap",
+ "newPasswordLabel": "Kata sandi baru",
+ "newPasswordHint": "Kosongkan untuk mempertahankan yang saat ini. Minimal 8 karakter jika diatur.",
+ "quotaLabel": "Quota (GB)",
+ "quotaHint": "0 atau kosong = tidak terbatas.",
+ "activeLabel": "Aktif",
+ "activeDesc": "Ketika tidak dicentang, login IMAP gagal dan mail masuk terpental."
+ },
+ "deleteForm": {
+ "title": "Hapus {username}?",
+ "descHard": "Hapus permanen: menghapus baris DB DAN Maildir di disk. Tidak dapat dibatalkan.",
+ "descSoft": "Hapus ringan: mengatur active = 0 dan menyisipkan baris di vmail.deleted_mailboxes. Cron pembersihan server mail akan mengambilnya nanti. Maildir tetap di disk sampai saat itu.",
+ "submitHard": "Hapus permanen mailbox",
+ "submitSoft": "Hapus ringan mailbox",
+ "submitting": "Menghapus…",
+ "hardDeleteLabel": "Hapus permanen",
+ "hardDeleteDesc": "Hapus baris DB dan file Maildir segera. Tidak dapat dibatalkan.",
+ "postmasterNote": "Mailbox postmaster dilindungi dari hapus permanen - hanya hapus ringan yang tersedia, sehingga akun bootstrap server mail tetap utuh."
+ }
+ },
+ "aliases": {
+ "heading": "Alias",
+ "description": "Teruskan mail dari satu alamat ke alamat lain, atau tangkap setiap alamat yang tidak cocok di sebuah domain. Aktif saat Anda menyimpan - tidak perlu restart.",
+ "addAlias": "Tambah alias",
+ "domainLabel": "Domain",
+ "loading": "Memuat…",
+ "activeCount": "{active} dari {total} aktif",
+ "colSource": "Sumber",
+ "colDestination": "Diteruskan ke",
+ "colType": "Tipe",
+ "colStatus": "Status",
+ "typeAlias": "Alias",
+ "typeCatchAll": "Tangkap semua",
+ "active": "Aktif",
+ "disabled": "Nonaktif",
+ "activateAction": "Aktifkan",
+ "deactivateAction": "Nonaktifkan",
+ "deleteAction": "Hapus",
+ "loadDomainsFailed": "Gagal memuat domain",
+ "loadFailed": "Gagal memuat alias",
+ "emptyTitle": "Belum ada alias",
+ "emptyDesc": "Teruskan sebuah alamat, atau siapkan tangkap semua, untuk {domain}.",
+ "create": {
+ "title": "Alias baru · {domain}",
+ "description": "Membuat baris di tabel penerusan server mail. Postfix membacanya secara langsung - tidak perlu restart.",
+ "submit": "Buat alias",
+ "submitting": "Membuat…",
+ "catchAllLabel": "Tangkap semua untuk domain ini",
+ "catchAllHint": "Meneruskan setiap alamat di {domain} yang tidak cocok dengan mailbox nyata atau alias lain. Menggantikan tangkap semua yang ada untuk domain ini.",
+ "localPartLabel": "Bagian lokal",
+ "localPartHint": "Bagian sebelum @. Huruf kecil, alfanumerik.",
+ "destinationLabel": "Diteruskan ke",
+ "destinationHint": "Setiap alamat email yang valid - mailbox di server ini, atau eksternal (mis. Gmail)."
+ },
+ "deleteForm": {
+ "title": "Hapus alias ini?",
+ "sourceCatchAll": "Tangkap semua untuk {domain}",
+ "description": "Menghapus baris penerusan segera. Mail ke {source} akan terpental atau jatuh ke aturan pencocokan berikutnya, berlaku pada pesan berikutnya.",
+ "submit": "Hapus alias",
+ "submitting": "Menghapus…"
+ },
+ "toast": {
+ "mailboxConflict": "{address} adalah mailbox nyata - membuat alias di sini akan mengarahkan mailnya. Edit atau hapus mailbox sebagai gantinya.",
+ "alreadyExists": "Penerusan yang persis sama sudah ada."
+ }
+ },
+ "health": {
+ "heading": "Kesehatan",
+ "description": "Status daemon langsung + pindai DNS. Daemon disegarkan dengan polling 10 detik; pindai DNS berjalan sekali saat dibuka dan lagi setiap kali Anda menekan Pindai Ulang.",
+ "healthCheckFailed": "Pemeriksaan kesehatan gagal",
+ "scanFailed": "Pindai DNS gagal",
+ "daemonsTitle": "Daemon",
+ "daemonsDesc": "Proses server mail yang dikelola oleh systemd di VPS.",
+ "updated": "diperbarui {time}",
+ "dnsScanTitle": "Pindai DNS",
+ "dnsScanDescFor": "Pemeriksaan DNS publik langsung untuk setiap record yang kami harapkan untuk {domain}.",
+ "dnsScanDesc": "Pemeriksaan DNS publik langsung untuk setiap record yang diharapkan server ini.",
+ "rescan": "Pindai Ulang",
+ "dnsEmpty": "Belum ada record DNS. Selesaikan wizard instalasi untuk mengisi pindaian.",
+ "delivery": {
+ "title": "Pengiriman outbound",
+ "desc": "Ke mana server ini menyerahkan mail, dan apakah ada yang macet di jalur.",
+ "sendPath": "Jalur kirim",
+ "direct": "Langsung ke penerima",
+ "directHint": "Pesan langsung dari server ini ke server mail masing-masing penerima.",
+ "relay": "Di-relay melalui {host}",
+ "relayAll": "Setiap pengirim di server ini mengirim melalui relay.",
+ "relaySelected": "Hanya pengirim terpilih yang di-relay: {domains}",
+ "queue": "Antrean",
+ "queueEmpty": "Kosong - tidak ada yang menunggu untuk dikirim.",
+ "queueOne": "{count} pesan menunggu untuk dikirim.",
+ "queueOther": "{count} pesan menunggu untuk dikirim.",
+ "sampled": "Antrean lebih besar dari jendela yang kami baca, sehingga alasan di bawah adalah yang terbaru.",
+ "reasons": "Mengapa mail ditunda",
+ "affectedOne": "{count} pesan",
+ "affectedOther": "{count} pesan",
+ "kind": {
+ "auth": "Autentikasi ditolak",
+ "tls": "Enkripsi gagal",
+ "network": "Tidak dapat terhubung",
+ "rejected": "Ditolak oleh penerima",
+ "other": "Ditunda"
+ },
+ "hint": {
+ "authRelay": "Relay Anda menolak kredensial ini. Masukkan ulang nama pengguna SMTP dan kata sandi di tab Mengirim.",
+ "auth": "Hop berikutnya menolak autentikasi.",
+ "tls": "TLS dengan hop berikutnya tidak dapat dinegosiasikan.",
+ "network": "Server ini tidak dapat menjangkau hop berikutnya. Periksa apakah port outbound 25 dan 587 terbuka.",
+ "rejected": "Penerima menolak pesan untuk saat ini. Postfix akan terus mencoba ulang.",
+ "other": "Postfix akan mencoba ulang sesuai jadwalnya sendiri."
+ },
+ "status": {
+ "ok": "Mengirim",
+ "warn": "Tertunda",
+ "fail": "Tidak mengirim",
+ "unknown": "Tidak Diketahui"
+ }
+ },
+ "logs": "Log",
+ "openLogs": "Buka log",
+ "up": "Hidup {time}",
+ "optional": "Opsional",
+ "daemonHint": {
+ "fatal": "supervisord sudah menyerah - tidak akan mencoba ulang. Restart dari menu.",
+ "backoff": "Crash berulang; supervisord masih mencoba ulang. Periksa log untuk mengetahui penyebabnya."
+ },
+ "menu": {
+ "restart": "Restart",
+ "restarting": "Merestart…",
+ "stop": "Hentikan",
+ "stopping": "Menghentikan…",
+ "start": "Mulai",
+ "starting": "Memulai…"
+ },
+ "toast": {
+ "started": "{label} dimulai",
+ "stopped": "{label} dihentikan",
+ "restarted": "{label} direstart",
+ "startFailed": "mulai gagal",
+ "stopFailed": "berhenti gagal",
+ "restartFailed": "restart gagal",
+ "startFailedTitle": "Mulai {label} gagal",
+ "stopFailedTitle": "Hentikan {label} gagal",
+ "restartFailedTitle": "Restart {label} gagal",
+ "notConfirmedTitle": "{label} belum menyala",
+ "notConfirmed": "Supervisor menerima permintaan, tetapi {label} dalam status {state}. Buka log untuk melihat penyebabnya."
+ },
+ "daemonStatus": {
+ "running": "Berjalan",
+ "starting": "Memulai",
+ "stopping": "Menghentikan",
+ "stopped": "Berhenti",
+ "failed": "Gagal",
+ "crashed": "Crash",
+ "missing": "Tidak Ditemukan",
+ "unknown": "Tidak Diketahui"
+ },
+ "dnsStatus": {
+ "pass": "Lolos",
+ "warning": "Peringatan",
+ "fail": "Gagal",
+ "unknown": "Tidak Diketahui"
+ },
+ "expected": "Diharapkan",
+ "actual": "Aktual",
+ "noRecord": "(tidak ada record)",
+ "summary": {
+ "allGoodLabel": "Semua sistem beroperasi",
+ "allGoodSub": "Daemon berjalan dan setiap record DNS dipublikasikan dengan benar.",
+ "almostLabel": "Hampir siap",
+ "almostSubOne": "{count} record DNS opsional dapat ditingkatkan - lihat pindai DNS di bawah.",
+ "almostSubOther": "{count} record DNS opsional dapat ditingkatkan - lihat pindai DNS di bawah.",
+ "notInstalledLabel": "{names} belum diinstal",
+ "notInstalledSubOne": "Mail tetap berfungsi tanpa itu; instal hanya jika Anda benar-benar membutuhkan komponen ini.",
+ "notInstalledSubOther": "Mail tetap berfungsi tanpa mereka; instal hanya jika Anda benar-benar membutuhkan komponen ini.",
+ "issuesLabel": "Masalah perlu perhatian",
+ "partDown": "{names} mati",
+ "partNotInstalled": "{names} belum diinstal",
+ "partDnsOne": "{count} record DNS hilang",
+ "partDnsOther": "{count} record DNS hilang",
+ "partDelivery": "mail outbound tidak terkirim",
+ "degradedLabel": "Berfungsi, dengan perlindungan berkurang",
+ "partScanning": "{names} tidak berjalan - mail baru dikirim TANPA pemindaian virus",
+ "partSignatures": "{names} tidak berjalan - tanda tangan virus tidak lagi diperbarui, sehingga mail dipindai dengan set tanda tangan yang menua",
+ "partAdvisory": "{names} tidak berjalan",
+ "almostSubQueueOne": "{count} pesan menunggu untuk dikirim - lihat Pengiriman outbound di bawah.",
+ "almostSubQueueOther": "{count} pesan menunggu untuk dikirim - lihat Pengiriman outbound di bawah."
+ },
+ "time": {
+ "secondsAgo": "{n}d lalu",
+ "minutesAgo": "{n}m lalu",
+ "hoursAgo": "{n}j lalu",
+ "daysAgo": "{n}h lalu"
+ }
+ },
+ "backup": {
+ "heading": "Backup",
+ "description": "Snapshot server mail ini ke salah satu tujuan backup Anda. Untuk memindahkan server, backup di sini, siapkan target dengan wizard instalasi, lalu pulihkan backup ini ke server baru.",
+ "schedules": {
+ "manual": "Manual saja",
+ "daily": "Harian",
+ "weekly": "Mingguan",
+ "monthly": "Bulanan",
+ "custom": "Kustom (cron)"
+ },
+ "whatTitle": "Apa yang dibackup",
+ "whatDesc": "Akun dan autentikasi selalu disertakan; data pesan dan kunci bersifat opsional.",
+ "accountsTitle": "Akun, domain & alias",
+ "accountsDesc": "Mailbox (dengan hash kata sandi), domain, dan alias — database vmail.",
+ "messageTitle": "Data pesan mailbox",
+ "messageDesc": "Setiap email yang tersimpan (maildir /var/vmail). Dapat berukuran besar.",
+ "keysTitle": "Kunci DKIM & rahasia",
+ "keysDesc": "Kunci DKIM + config + mail-state.json. Menjaga DKIM valid saat pemulihan — tetapi arsip kemudian menyimpan kunci pribadi dan rahasia.",
+ "keysWarning": "Backup akan berisi hash kata sandi, kunci pribadi DKIM, dan rahasia teks biasa. Pastikan tujuan bersifat privat.",
+ "destTitle": "Tujuan & jadwal",
+ "destDesc": "Backup dialirkan ke tujuan yang Anda kelola di halaman Backup.",
+ "noDest": "Belum ada tujuan backup. Tambahkan satu (mis. server yang sudah ada) di halaman Backup.",
+ "destinationLabel": "Tujuan",
+ "autoLabel": "Backup otomatis",
+ "timeLabel": "Waktu",
+ "timeHint": "Waktu server",
+ "weekdayLabel": "Hari dalam minggu",
+ "dayOfMonthLabel": "Tanggal dalam bulan",
+ "cronLabel": "Ekspresi cron",
+ "cronHint": "Lima bidang, dievaluasi di zona waktu server.",
+ "scheduleJobHint": "Backup otomatis berjalan sebagai pekerjaan terjadwal.",
+ "manageDest": "Kelola",
+ "retentionTitle": "Retensi",
+ "retentionDesc": "Berapa banyak riwayat yang disimpan di tujuan. Backup yang lebih lama dihapus oleh pembersihan harian, file dan semua.",
+ "keepCountLabel": "Pertahankan",
+ "keepCountUnit": "backup terakhir",
+ "keepDaysLabel": "Hapus setelah",
+ "keepDaysUnit": "hari",
+ "retentionNoLimit": "Tidak ada batas",
+ "retentionUnlimitedWarning": "Tidak ada yang pernah dihapus — tujuan ini akan terus bertambah.",
+ "weekdays": {
+ "sun": "Minggu",
+ "mon": "Senin",
+ "tue": "Selasa",
+ "wed": "Rabu",
+ "thu": "Kamis",
+ "fri": "Jumat",
+ "sat": "Sabtu"
+ },
+ "saveSettings": "Simpan pengaturan",
+ "backupNow": "Backup sekarang",
+ "recentTitle": "Backup terbaru",
+ "noBackups": "Belum ada backup.",
+ "restore": "Pulihkan",
+ "migrate": "Migrasi",
+ "toastTitle": "Backup",
+ "pickDest": "Pilih tujuan backup terlebih dahulu.",
+ "scheduleIncomplete": "Pilih waktu untuk jadwal ini, atau atur kembali ke manual.",
+ "saved": "Pengaturan backup tersimpan.",
+ "saveFailed": "Tidak dapat menyimpan pengaturan backup",
+ "started": "Backup dimulai. Akan muncul di bawah saat berjalan.",
+ "startFailed": "Tidak dapat memulai backup"
+ },
+ "advanced": {
+ "resetTitle": "Atur ulang status di server?",
+ "resetDesc": "Menghapus pelacakan openship atas instalasi ini: file status di server (/root/.openship/mail-state.json) DAN baris registry server mail. Stack mail yang berjalan tidak tersentuh — setiap mailbox, pesan, dan antrean tetap utuh — tetapi Anda perlu menjalankan ulang wizard pengaturan untuk mengelolanya lagi (Hapus mempertahankan file status sehingga Anda dapat mengadopsi ulang).",
+ "resetSubmit": "Atur ulang status",
+ "resetSubmitting": "Mengatur ulang…",
+ "resetFailed": "Reset gagal",
+ "resetBox": "Setelah ini, halaman /emails akan menampilkan wizard instalasi lagi untuk server ini. Anda kemudian dapat menjalankan ulang dari langkah 1 atau mengambil dari langkah tertentu.",
+ "forgetTitle": "Hapus server mail ini?",
+ "forgetDesc": "Hapus server ini dari daftar mail openship saja. Stack mail tetap berjalan dan tidak ada yang di-uninstall — tidak ada mailbox, pesan, atau DNS yang tersentuh. Tambahkan kembali kapan saja dengan Pindai untuk instalasi yang sudah ada.",
+ "forgetSubmit": "Hapus dari daftar",
+ "forgetSubmitting": "Menghapus…",
+ "forgetFailed": "Hapus gagal",
+ "forgetBox": "Gunakan ini untuk menghapus entri yang sudah usang atau salah ditandai. Karena instalasi di server dibiarkan utuh, mengadopsi ulang nanti akan memulihkan manajemen penuh tanpa reinstalasi.",
+ "protocolTitle": "Pengaturan protokol",
+ "protocolDesc": "Host, port, dan enkripsi untuk inbound (IMAP) dan outbound (SMTP). Gunakan ini saat mengonfigurasi klien mail secara manual - sebagian besar klien juga dapat menemukannya dari alamat email saja.",
+ "incoming": "Masuk · IMAP",
+ "outgoing": "Keluar · SMTP",
+ "host": "Host",
+ "port": "Port",
+ "security": "Keamanan",
+ "protocolNote": {
+ "p1": "Nama pengguna di kedua server adalah ",
+ "emailAddress": "alamat email lengkap",
+ "p2": " - mis. ",
+ "p3": ". Atur kata sandi dari ",
+ "credPath": "Ringkasan → Kredensial Postmaster → Ubah kata sandi",
+ "p4": "; openship tidak pernah menyimpan teks biasa, jadi gunakan pengelola kata sandi (1Password, Bitwarden, dll.) untuk menyimpannya."
+ },
+ "dangerTitle": "Zona bahaya",
+ "dangerDesc": "Tindakan ini dapat mengganggu server mail yang berjalan atau menghapus status pelacakan penting. Baca deskripsi di setiap kartu sebelum mengklik.",
+ "rerunTitle": "Jalankan ulang wizard pengaturan",
+ "rerunDesc": "Membuka wizard instalasi yang ditunjuk ke server ini. Berguna setelah perubahan DNS, pergantian nama domain, atau untuk mencoba ulang langkah yang gagal. Wizard mendeteksi instalasi yang sudah ada dan menawarkan pencobaan ulang per langkah alih-alih menghapus status.",
+ "openWizard": "Buka wizard",
+ "resetCardTitle": "Atur ulang status di server",
+ "resetCardDesc": "Menghapus pelacakan openship atas instalasi ini — file status di server (/root/.openship/mail-state.json) dan baris registry. TIDAK meng-uninstall stack mail atau menyentuh mailbox apa pun; server tetap berjalan, tetapi Anda akan menjalankan ulang wizard pengaturan untuk mengelolanya lagi. Gunakan setelah pembersihan manual atau re-image ketika pelacakan sudah menyimpang dari kenyataan.",
+ "resetStateBtn": "Atur ulang status",
+ "removeTitle": "Hapus server mail",
+ "removeDesc": "Berhenti mengelola server ini — menghapusnya dari daftar /emails saja. Stack mail tetap berjalan dan file status di server dibiarkan utuh, sehingga Anda dapat mengadopsi ulang nanti melalui Pindai untuk instalasi yang sudah ada. Gunakan untuk menghapus entri yang sudah usang atau salah ditandai.",
+ "remove": "Hapus",
+ "metaTitle": "Metadata instalasi",
+ "metaServerId": "ID Server",
+ "metaPrimaryDomain": "Domain primer",
+ "metaStartedAt": "Dimulai pada",
+ "metaFinishedAt": "Selesai pada",
+ "toolsTitle": "Alat stack mail",
+ "toolsDesc": "Tindakan pemulihan yang menyentuh stack yang berjalan tanpa mengubah status. Aman dijalankan kapan saja; aliran mail berkedip beberapa detik.",
+ "restartCardTitle": "Restart stack mail",
+ "restartCardDesc": "Meng-cycle setiap daemon mail sekaligus. Perbaikan tercepat ketika sesuatu macet setelah deploy — login gagal, antrean macet, dll.",
+ "restartStack": "Restart stack",
+ "restartConfirmTitle": "Restart stack mail?",
+ "restartConfirmDesc": "Meng-cycle setiap daemon mail yang berjalan (penerima antrean, layanan IMAP, pipeline spam, fail2ban, dan PostgreSQL). Aliran mail berhenti beberapa detik saat unit kembali menyala. Mailbox, antrean, dan DNS tidak tersentuh.",
+ "restartSubmitting": "Merestart…",
+ "restartBox": "Gunakan ini ketika ada yang salah tepat setelah deploy atau perubahan konfigurasi — sebagian besar kerusakan sementara hilang dengan satu cycle.",
+ "restartedToast": "Merestart {count} daemon mail.",
+ "restartedTitle": "Stack mail direstart",
+ "partialToast": "{failed} dari {total} gagal: {units}",
+ "partialTitle": "Restart sebagian",
+ "restartFailed": "Restart gagal",
+ "restartFailedTitle": "Restart stack mail gagal"
+ },
+ "test": {
+ "title": "Uji pengiriman",
+ "description": "Kirim email uji nyata melalui server mail ini untuk mengonfirmasi pengiriman, DKIM, dan SPF.",
+ "sendButton": "Kirim email uji"
+ },
+ "reputation": {
+ "titlePrefix": "Pemanasan reputasi untuk ",
+ "body": "Domain pengirim yang baru belum memiliki reputasi, sehingga penyedia mungkin mengirim mail awal ke spam selama 24-48 jam pertama. Ini normal. Tandai pesan sebagai \"bukan spam\" dari kotak masuk pengujian Anda dan pengiriman akan membaik dengan cepat. ",
+ "dismissLink": "Saya tahu, sembunyikan ini",
+ "dismiss": "Tutup"
+ },
+ "engine": {
+ "downTitle": "Mesin mail di server ini tidak berjalan",
+ "downBody": "Saat berhenti, tidak ada mail yang dikirim atau diterima, dan setiap tab di sini membutuhkannya. Memulai ulang aman — tidak ada yang diinstal ulang dan tidak ada konfigurasi yang ditulis ulang.",
+ "legacyNote": "Server ini menjalankan mesin legacy host-native (systemd Postfix/Dovecot), sehingga tidak ada container untuk diperiksa.",
+ "start": "Mulai mesin",
+ "missingTitle": "Tidak ada mesin mail di server ini",
+ "missingBody": "Openship tidak dapat menemukan mesin mail di sini — baik container mesin maupun unit Postfix/Dovecot. Jalankan ulang pengaturan mail untuk menginstalnya lagi.",
+ "rerunSetup": "Jalankan ulang pengaturan mail",
+ "cardBlocked": "Tidak tersedia sampai mesin mail berjalan."
+ },
+ "welcome": {
+ "invalidEmail": "Masukkan alamat email yang valid",
+ "sendFailed": "Gagal mengirim",
+ "dismiss": "Tutup",
+ "liveTitle": "Server mail Anda sudah live.",
+ "liveDescAfter": " menerima mail. Kirim email uji ke diri sendiri untuk mengonfirmasi.",
+ "sendToLabel": "Kirim uji ke",
+ "skip": "Lewati",
+ "sending": "Mengirim",
+ "sendTest": "Kirim uji",
+ "sentTitle": "Dalam perjalanan.",
+ "sentBefore": "Email uji dari ",
+ "sentMiddle": " sedang menuju ke ",
+ "sentAfter": ". Seharusnya tiba dalam satu menit - periksa kotak masuk dan folder spam Anda.",
+ "gotIt": "Mengerti"
+ },
+ "restore": {
+ "titleMigrate": "Migrasi ke server lain",
+ "titleRestore": "Pulihkan backup",
+ "startFailed": "Tidak dapat memulai pemulihan",
+ "applyFailed": "Tidak dapat menerapkan pemulihan",
+ "reviewBefore": "Memulihkan backup dari ",
+ "reviewOntoFork": " ke server mail lain.",
+ "reviewOntoInPlace": " ke server mail ini.",
+ "targetServer": "Server target",
+ "noTargetsBefore": "Tidak ada server mail lain yang diinstal. Siapkan target dengan wizard instalasi terlebih dahulu (domain yang sama, ",
+ "noTargetsAfter": "), lalu akan muncul di sini.",
+ "selectServer": "Pilih server…",
+ "warnMain": "Ini akan menghapus akun, domain, dan alias server {which}, lalu memuat backup.",
+ "warnWhichTarget": "target",
+ "warnWhichCurrent": "saat ini",
+ "warnForkExtra": "Target harus diatur dengan domain yang sama. Setelah migrasi, arahkan ulang MX/A DNS + PTR ke server baru.",
+ "confirmBefore": "Ketik ",
+ "confirmAfter": " untuk mengonfirmasi",
+ "close": "Tutup",
+ "cancel": "Batal",
+ "migrate": "Migrasi",
+ "restore": "Pulihkan",
+ "progressComplete": "Pemulihan selesai",
+ "progressFailed": "Pemulihan {status}",
+ "startingFallback": "memulai",
+ "subDone": "{domain} dipulihkan. Daemon mail dimuat ulang.",
+ "subFailed": "Lihat error di atas.",
+ "subRunning": "Memindahkan akun, domain, dan data pesan. Ini mungkin memakan waktu."
+ },
+ "sendTest": {
+ "openGmail": "Buka Gmail",
+ "openOutlook": "Buka Outlook",
+ "openYahoo": "Buka Yahoo Mail",
+ "openIcloud": "Buka iCloud Mail",
+ "openProton": "Buka Proton Mail",
+ "openAol": "Buka AOL Mail",
+ "loadDomainsFailed": "Gagal memuat domain",
+ "sentToast": "Email uji terkirim. Periksa kotak masuk + spam.",
+ "toastTitle": "Email uji",
+ "sendFailed": "Gagal mengirim email uji",
+ "close": "Tutup",
+ "title": "Kirim email uji",
+ "sendsFromBefore": "Mengirim dari ",
+ "sendsFromAfter": " — mailbox disiapkan saat diminta untuk domain ini dan ditandatangani DKIM dengan kunci domain.",
+ "sendToLabel": "Kirim ke",
+ "fromDomainLabel": "Dari domain",
+ "loadingDomains": "Memuat domain…",
+ "noActiveDomains": "Tidak ada domain aktif",
+ "fromHint": "Pilih domain untuk autentikasi. Berguna untuk memverifikasi domain yang baru ditambahkan setelah record DNS-nya propogated.",
+ "cancel": "Batal",
+ "sending": "Mengirim…",
+ "sendTest": "Kirim uji",
+ "sentTitle": "Email uji terkirim.",
+ "sentCheckBefore": "Periksa kotak masuk + spam di ",
+ "sentCheckMiddle": ". Dikirim dari ",
+ "sentCheckAfter": "."
+ },
+ "shared": {
+ "cancel": "Batal",
+ "actionFailed": "Aksi gagal",
+ "logsTitle": "Log {label}",
+ "reload": "Muat ulang",
+ "close": "Tutup",
+ "logsLoadFailed": "Gagal memuat log",
+ "loadingLogs": "Memuat log…",
+ "noLogLines": "Belum ada baris log untuk komponen ini.",
+ "rowActions": "Tindakan untuk {name}"
+ }
+}
\ No newline at end of file
diff --git a/apps/dashboard/src/i18n/locales/id/importProject.json b/apps/dashboard/src/i18n/locales/id/importProject.json
new file mode 100644
index 000000000..b0c5996bd
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/id/importProject.json
@@ -0,0 +1,443 @@
+{
+ "counts": {
+ "portOne": "{count} port",
+ "portOther": "{count} port",
+ "volumeOne": "{count} volume",
+ "volumeOther": "{count} volume",
+ "serviceOne": "{count} layanan",
+ "serviceOther": "{count} layanan",
+ "depOne": "{count} dep",
+ "depOther": "{count} dep",
+ "variableOne": "{count} variabel",
+ "variableOther": "{count} variabel"
+ },
+ "categories": {
+ "frontend": "Frontend",
+ "backend": "Backend",
+ "fullstack": "Fullstack",
+ "static": "Statis",
+ "docker": "Docker",
+ "services": "Layanan",
+ "generic": "Umum"
+ },
+ "serviceStatus": {
+ "running": "Berjalan",
+ "built": "Sudah Dibangun",
+ "building": "Sedang Dibangun",
+ "deploying": "Sedang Di-deploy",
+ "failed": "Gagal",
+ "pending": "Tertunda"
+ },
+ "buildSkeleton": {
+ "loading": "Memuat sesi build..."
+ },
+ "dockerSettings": {
+ "title": "Dockerfile Terdeteksi",
+ "subtitle": "Proyek Anda akan dibangun dan di-deploy menggunakan Dockerfile-nya",
+ "infoCard": "Tidak perlu pengaturan build - Dockerfile Anda mendefinisikan proses build. Cukup konfigurasi port yang didengarkan aplikasi Anda.",
+ "exposedPort": "Port Terekspos",
+ "portHint": "Port yang diekspos container Anda (dari EXPOSE atau CMD)"
+ },
+ "buildSettings": {
+ "installCommandLabel": "Perintah Instalasi",
+ "installCommandDesc": "Perintah untuk menginstal dependensi",
+ "buildCommandLabel": "Perintah Build",
+ "buildCommandDesc": "Perintah untuk membangun proyek Anda",
+ "outputDirectoryLabel": "Direktori Output",
+ "outputDirectoryDesc": "Direktori dengan output build",
+ "sourceFolderLabel": "Folder Sumber",
+ "sourceFolderDesc": "Build dari subdirektori di dalam repository atau proyek lokal.",
+ "composePath": {
+ "title": "File compose",
+ "subtitle": "Terdeteksi secara otomatis di root proyek",
+ "label": "Path ke file compose Anda",
+ "description": "Untuk repo yang menyimpan compose di luar root — berikan file (deploy/stack.yml) atau folder yang menyimpannya (deploy/docker-compose). Memindai ulang repo dan mendeploy layanan yang dideklarasikannya.",
+ "apply": "Pindai",
+ "scanning": "Memindai…",
+ "scanFailed": "Tidak dapat memindai path compose tersebut.",
+ "subtitleOptIn": "Mendeploy dengan Docker Compose? Arahkan ke file-nya."
+ },
+ "healthCheck": {
+ "title": "Pemeriksaan kesehatan",
+ "subtitle": "Nonaktif — deployment selesai begitu aplikasi Anda berjalan",
+ "subtitleOn": "Aktif — deployment menunggu aplikasi Anda sebelum go live",
+ "description": "Openship tidak menunggu aplikasi Anda setelah dimulai, sehingga boot yang lambat tidak pernah menunda atau gagalkan deployment. Begitu live, port tetap diperiksa dan dilaporkan tanpa memblokir apa pun. Aktifkan ini hanya jika Anda ingin deployment itu sendiri menolak untuk go live.",
+ "probeLabel": "Tunggu aplikasi merespons",
+ "probeDesc": "Setelah container dimulai, hubungi port aplikasi dan tunggu hingga menerima koneksi.",
+ "pathLabel": "Path HTTP",
+ "pathPlaceholder": "/health",
+ "pathDesc": "Minta GET di sini untuk merespons di bawah 500. Kosongkan untuk menerima koneksi TCP apa pun, yang juga berfungsi untuk layanan non-HTTP.",
+ "timeoutLabel": "Berikan waktu (detik)",
+ "stabilizationLabel": "Pantau loop restart",
+ "stabilizationDesc": "Pantau container setelah dimulai dan laporkan crash loop. Membaca runtime, sehingga ini berfungsi di server remote juga.",
+ "stabilizationWindowLabel": "Pantau selama (detik)",
+ "onFailureLabel": "Jika pemeriksaan gagal",
+ "onFailureWarn": "Pertahankan deployment live dan tandai untuk ditinjau",
+ "onFailureFail": "Gagalkan deployment dan pertahankan versi sebelumnya melayani"
+ },
+ "buildImageLabel": "Gambar Build",
+ "buildImageDesc": "Gambar container builder untuk build cloud atau server. Override jika gambar base yang terdeteksi salah.",
+ "productionPathsLabel": "Path Produksi",
+ "productionPathsDesc": "Hanya deploy file/direktori ini setelah build - sembunyikan kode sumber dari runtime. Kosongkan untuk menjalankan di tempat.",
+ "startCommandLabel": "Perintah Start",
+ "startCommandDesc": "Perintah untuk memulai aplikasi Anda",
+ "productionPortDesc": "Port produksi untuk aplikasi Anda",
+ "productionPort": "Port Produksi",
+ "portForPrefix": "Port untuk",
+ "pathForPrefix": "Path untuk",
+ "enterPort": "Masukkan port",
+ "optional": "(Opsional)",
+ "save": "Simpan",
+ "cancel": "Batal",
+ "deployConfig": "Konfigurasi Deploy",
+ "defaultsApplied": "Default {framework} diterapkan",
+ "configureOptions": "Konfigurasi opsi build",
+ "build": "Build",
+ "installBuildIn": "Instal & build di {image}",
+ "installBuildCommands": "Perintah instal & build",
+ "deploySourceDirectly": "Deploy sumber langsung",
+ "advanced": "Lanjutan",
+ "start": "Mulai",
+ "serverOnPort": "Server di port {port}",
+ "serverPortNotSet": "Port server belum diatur",
+ "staticFromEdge": "Statis dari edge",
+ "staticPathHint": "Disajikan sebagai file statis dari edge — tidak ada yang berjalan sebagai proses.",
+ "modeServer": "Server",
+ "modeWorker": "Worker",
+ "modeStatic": "Statis",
+ "workerModeDesc": "Berjalan terus-menerus tanpa port atau URL publik — konsumen antrean, cron, atau bot.",
+ "buildSettingsTitle": "Pengaturan Build"
+ },
+ "projectSettings": {
+ "framework": "Framework",
+ "detected": "Terdeteksi",
+ "change": "Ubah",
+ "useDetected": "Gunakan yang terdeteksi",
+ "selectHint": "Pilih framework Anda untuk mengonfigurasi pengaturan build secara otomatis"
+ },
+ "routingSection": {
+ "title": "Routing",
+ "descDetected": "Rewrite, redirect & header — dilayani di satu domain."
+ },
+ "environmentVariables": {
+ "title": "Variabel Lingkungan",
+ "noneSet": "Belum diatur",
+ "pasteEnv": "Tempel .env",
+ "uploadEnv": "Unggah .env",
+ "edit": "Edit",
+ "cancel": "Batal",
+ "saving": "Menyimpan...",
+ "saveChanges": "Simpan Perubahan",
+ "collapse": "Ciutkan variabel lingkungan",
+ "expand": "Perluas variabel lingkungan",
+ "valuePlaceholder": "nilai",
+ "dropHere": "Jatuhkan file .env di sini",
+ "noneTitle": "Tidak ada variabel lingkungan",
+ "emptyHintEditing": "Tempel blok .env di mana saja di kotak ini, atau jatuhkan file .env di sini.",
+ "emptyHintReadonly": "Klik \"Edit\" untuk mengelola variabel lingkungan",
+ "pasteHint": "Tempel blok .env lengkap di mana saja di dalam kotak ini.",
+ "addVariable": "Tambah Variabel",
+ "delete": "Hapus",
+ "resolution": {
+ "needsValue": "Membutuhkan nilai",
+ "fallbackDefault": "Default cadangan",
+ "loadedFromEnv": "Dimuat dari .env",
+ "interpolated": "Diinterpolasi"
+ },
+ "toast": {
+ "title": "Variabel Lingkungan",
+ "added": "{count} ditambahkan",
+ "updated": "{count} diperbarui",
+ "pastedOne": "Ditempel {count} variabel{detail}",
+ "pastedOther": "Ditempel {count} variabel{detail}",
+ "portSet": "Port runtime diatur ke {port} dari PORT",
+ "clipboardUnavailable": "Akses clipboard tidak tersedia di sini. Klik di dalam kotak lingkungan dan tempel dengan Cmd/Ctrl+V.",
+ "clipboardEmpty": "Clipboard kosong",
+ "clipboardInvalid": "Clipboard tidak berisi variabel lingkungan KEY=VALUE yang valid",
+ "clipboardBlocked": "Akses clipboard diblokir. Klik di dalam kotak lingkungan dan tempel dengan Cmd/Ctrl+V."
+ },
+ "reveal": {
+ "show": "Tampilkan nilai",
+ "hide": "Sembunyikan nilai",
+ "error": "Tidak dapat menampilkan nilai"
+ }
+ },
+ "composeServices": {
+ "domain": {
+ "internalService": "Layanan internal",
+ "noPublicPorts": "Tidak ada port publik yang terdeteksi",
+ "publicDomain": "Domain publik",
+ "internetEnabled": "Lalu lintas internet diaktifkan",
+ "privateByDefault": "Privat secara default",
+ "exposedPort": "Port Terekspos",
+ "portOption": "Port {port}",
+ "domainLabel": "Domain",
+ "free": "Gratis",
+ "custom": "Kustom",
+ "routingTrafficPrefix": "Merutekan lalu lintas ke port",
+ "duplicateTitle": "Domain duplikat",
+ "duplicateDescription": "Dua route mengarah ke domain yang sama — ubah salah satu sehingga setiap route memiliki domain sendiri:"
+ },
+ "shared": {
+ "title": "Lingkungan bersama",
+ "rootEnvFound": "Root .env ditemukan",
+ "optionalVars": "Variabel opsional yang diterapkan ke setiap layanan",
+ "sharedVariableOne": "{count} variabel bersama",
+ "sharedVariableOther": "{count} variabel bersama",
+ "overrideNote": "· nilai layanan menimpa nilai bersama",
+ "importEnv": "Impor .env",
+ "manage": "Kelola",
+ "modalSubtitle": "Diterapkan ke setiap layanan. Variabel layanan menang saat terjadi konflik.",
+ "close": "Tutup lingkungan bersama",
+ "importFromRootOne": "Impor {count} variabel dari root .env",
+ "importFromRootOther": "Impor {count} variabel dari root .env"
+ },
+ "config": {
+ "title": "Konfigurasi",
+ "summary": "{ports} · {volumes}",
+ "ports": "Port",
+ "containerLabel": "Container",
+ "publishedLabel": "Diterbitkan",
+ "equalsInsideService": "= di dalam layanan ·",
+ "equalsReachableHost": "= dapat dijangkau di host.",
+ "cloudPortIgnored": "Di cloud port yang diterbitkan diabaikan — hanya port container yang dirutekan.",
+ "naOnCloud": "tidak berlaku di cloud",
+ "publicBadge": "publik",
+ "removePort": "Hapus port",
+ "addPort": "Tambah port",
+ "volumes": "Volume",
+ "statefulWarnPart1": "Layanan berstatus di cloud: data berada di disk persisten workspace (bertahan saat restart) tetapi ",
+ "statefulWarnBold": "tidak dibawa melewati rebuild",
+ "statefulWarnPart2": " kecuali backup diaktifkan. Mount volume Docker tidak berlaku.",
+ "volumeHint": "Volume bernama atau path host → path container.",
+ "volumeCloudNote": "Di cloud mount ini tidak berlaku — data persist di disk workspace (tidak melewati rebuild tanpa backup).",
+ "sourcePlaceholder": "Sumber",
+ "containerPathPlaceholder": "Path container",
+ "readOnlyMount": "Mount hanya-baca",
+ "removeVolume": "Hapus volume",
+ "addVolume": "Tambah volume",
+ "command": "Perintah",
+ "imageDefault": "(default gambar)",
+ "restartPolicy": "Kebijakan restart",
+ "restartDefault": "Default (unless-stopped)"
+ },
+ "status": {
+ "public": "Publik",
+ "private": "Privat",
+ "internal": "Internal"
+ },
+ "card": {
+ "buildLabel": "Build: {value}",
+ "edit": "Edit",
+ "delete": "Hapus",
+ "envVars": "Variabel lingkungan",
+ "noneConfigured": "Belum dikonfigurasi",
+ "variablesConfiguredOne": "{count} variabel dikonfigurasi",
+ "variablesConfiguredOther": "{count} variabel dikonfigurasi",
+ "missingSuffix": "· {count} hilang",
+ "manage": "Kelola",
+ "variableSuffixOne": "· {count} variabel",
+ "variableSuffixOther": "· {count} variabel",
+ "closeEnv": "Tutup variabel lingkungan",
+ "needsValueOne": "{count} variabel lingkungan membutuhkan nilai",
+ "needsValueOther": "{count} variabel lingkungan membutuhkan nilai"
+ },
+ "main": {
+ "deployingServices": "Mendeploy sebagai layanan",
+ "deployingSingle": "Mendeploy sebagai satu aplikasi",
+ "buildCountSuffix": "· {count} build",
+ "exposedCountSuffix": "· {count} terekspos",
+ "infoPart1": "Layanan internal dapat saling menjangkau berdasarkan nama layanan. Aktifkan ",
+ "infoBold": "Domain publik",
+ "infoPart2": " hanya untuk layanan yang seharusnya menerima lalu lintas internet.",
+ "singleNote": "Layanan compose yang diurai disimpan untuk nanti, tetapi deployment ini akan menggunakan build, perintah start, lingkungan, dan pengaturan domain aplikasi tunggal biasa.",
+ "modeServicesLabel": "Stack layanan",
+ "modeServicesDesc": "Deploy setiap layanan compose dengan runtime dan domain-nya sendiri.",
+ "modeSingleLabel": "Aplikasi tunggal",
+ "modeSingleDesc": "Gunakan alur build dan perintah start biasa untuk satu aplikasi.",
+ "deploymentMode": "Mode deployment",
+ "deploymentModeDesc": "{mode} · Beralih antara stack layanan dan penanganan aplikasi tunggal."
+ }
+ },
+ "monorepo": {
+ "workspace": {
+ "title": "Workspace",
+ "subtitle": "Berjalan sekali di root repo sebelum setiap aplikasi dibangun — instalasi, codegen, sinkronisasi skema, dll.",
+ "packageManager": "Manajer paket",
+ "prepareCommand": "Perintah persiapan",
+ "chainPrefix": "Rantai dengan",
+ "chainSuffix": "untuk codegen, sinkronisasi skema, dll."
+ },
+ "app": {
+ "collapse": "Ciutkan",
+ "expand": "Perluas",
+ "portSuffix": "· port {port}",
+ "deployAria": "Deploy sub-aplikasi ini",
+ "deploy": "Deploy"
+ },
+ "header": {
+ "title": "Monorepo - {count} aplikasi terdeteksi",
+ "singleDesc": "Mendeploy sebagai satu aplikasi - satu container, satu set perintah, semua port terekspos.",
+ "multiDesc": "{selected} dari {total} dipilih. Setiap aplikasi di-deploy ke port dan domain-nya sendiri, berbagi satu instalasi workspace di root repo."
+ },
+ "singleNote": "Definisi sub-aplikasi disimpan untuk nanti. Deployment ini menggunakan build dan perintah start aplikasi tunggal biasa di bawah - setiap port sub-aplikasi yang diaktifkan diekspos dari runtime yang sama.",
+ "modes": {
+ "servicesLabel": "Runtime per-aplikasi",
+ "servicesDesc": "Deploy setiap sub-aplikasi dengan runtime, port, dan domain-nya sendiri.",
+ "singleLabel": "Aplikasi tunggal",
+ "singleDesc": "Promosikan build + perintah start satu sub-aplikasi, ekspos port setiap aplikasi dari satu runtime."
+ },
+ "deploymentMode": "Mode deployment",
+ "deploymentModeDesc": "{mode} · Beralih antara runtime per-aplikasi dan penanganan aplikasi tunggal."
+ },
+ "deploymentProcessing": {
+ "targetOpenshipCloud": "Openship Cloud",
+ "targetServerNamed": "Server · {name}",
+ "targetServer": "Server",
+ "targetLocal": "Mesin lokal",
+ "strategyLocal": "Build lokal",
+ "strategyCloud": "Build cloud",
+ "strategyServer": "Build server",
+ "strategyHost": "Build host",
+ "promptDetails": {
+ "port": "Port",
+ "process": "Proses",
+ "unitDescription": "Deskripsi Unit",
+ "openshipDeployment": "Deployment Openship",
+ "sites": {
+ "lead": "{count} situs saat ini dilayani oleh proxy yang ada. Migrasi mengimpornya ke Openship sebelum mengambil alih port 80/443:",
+ "tlsBadge": "TLS",
+ "staticLabel": "statis:",
+ "warningsTitle": "Tidak dapat dimigrasi secara otomatis — tambahkan kembali secara manual:"
+ }
+ },
+ "portAdvisory": {
+ "title": "Apakah ini port yang benar?",
+ "bannerTitle": "Port mungkin salah dikonfigurasi",
+ "review": "Tinjau",
+ "note": "Routing menunjuk ke port ini — jika salah, request akan gagal dengan 502.",
+ "body": "Tidak ada yang merespons di port {port}. Jika aplikasi Anda mendengarkan di port yang berbeda, atur yang benar di sini.",
+ "bodyService": "Layanan \"{service}\": tidak ada yang merespons di port {port}. Jika mendengarkan di port yang berbeda, atur yang benar di sini.",
+ "newPortLabel": "Port yang benar",
+ "changePort": "Ubah port",
+ "skip": "Lewati",
+ "applied": "Routing diperbarui ke port {port}.",
+ "changeError": "Tidak dapat memperbarui port. Silakan coba lagi."
+ },
+ "title": {
+ "cancelled": "Deployment Dibatalkan",
+ "failed": "Deployment Gagal",
+ "readyWarnings": "Deployment Siap dengan Peringatan",
+ "successful": "Deployment Berhasil",
+ "deploying": "Mendeploy..."
+ },
+ "visitSite": "Kunjungi Situs",
+ "warningTitle": "Deployment selesai, tetapi beberapa layanan masih membutuhkan perhatian.",
+ "productionLogs": "Log Produksi",
+ "buildTerminal": "Terminal Build",
+ "seeLogs": "Lihat log untuk detail masalah",
+ "redeploying": "Redeploy…",
+ "stopping": "Menghentikan…",
+ "stopDeployment": "Hentikan Deployment",
+ "redeploy": "Deploy Ulang",
+ "goToProject": "Buka proyek",
+ "openDashboard": "Buka Dashboard",
+ "edit": "Edit",
+ "status": {
+ "cancelled": "Dibatalkan",
+ "failed": "Gagal",
+ "readyWarnings": "Siap dengan peringatan",
+ "ready": "Siap",
+ "building": "Sedang Dibangun"
+ },
+ "detailsTitle": "Detail Deployment",
+ "detailStatus": "Status",
+ "detailInstance": "Instance",
+ "detailBuild": "Build",
+ "detailBuildTime": "Waktu build",
+ "detailFramework": "Framework",
+ "detailBranch": "Branch",
+ "detailDomains": "Domain",
+ "detailDomain": "Domain"
+ },
+ "composeDeployment": {
+ "title": {
+ "cancelled": "Deployment Dibatalkan",
+ "failed": "Deployment Gagal",
+ "actionRequired": "Tindakan Diperlukan",
+ "warnings": "Di-deploy dengan Peringatan",
+ "successful": "Deployment Berhasil",
+ "deploying": "Mendeploy Layanan…"
+ },
+ "decisionBannerTitle": "Tindakan diperlukan — beberapa layanan gagal",
+ "decisionBannerDefaultMsg": "Deployment ini selesai dengan layanan yang gagal. Pertahankan dan perbaiki nanti, atau tolak untuk memulihkan versi sebelumnya.",
+ "review": "Tinjau",
+ "warningTitle": "Beberapa layanan membutuhkan perhatian",
+ "redeploying": "Redeploy…",
+ "stopping": "Menghentikan…",
+ "stopDeployment": "Hentikan Deployment",
+ "redeploy": "Deploy Ulang",
+ "openDashboard": "Buka Dashboard",
+ "editConfiguration": "Edit Konfigurasi",
+ "logsTitle": "Log Deployment",
+ "logsSubtitle": "Output langsung, dialirkan per layanan",
+ "prepareTab": "Siapkan",
+ "noLogsFor": "Tidak ada log yang direkam untuk {service}.",
+ "waitingFor": "Menunggu log {service}...",
+ "noPrepareLogs": "Tidak ada log persiapan yang direkam.",
+ "preparingContext": "Mengkloning sumber dan menyiapkan konteks build...",
+ "preparingServiceLogs": "Menyiapkan log layanan...",
+ "toast": {
+ "keptMsg": "Deployment dipertahankan",
+ "keptTitle": "Dipertahankan",
+ "rejectedMsg": "Deployment sebagian ditolak",
+ "rejectedTitle": "Deployment Dikembalikan",
+ "retryFailMsg": "Tidak dapat memulai percobaan ulang",
+ "retryTitle": "Coba Ulang"
+ },
+ "partial": {
+ "title": "Deployment selesai dengan layanan yang gagal",
+ "body": "{failed} dari {total} layanan gagal, tetapi sisa stack berhasil di-deploy. Anda dapat mempertahankan deployment ini dan memperbaiki layanan yang gagal nanti, atau menolaknya dan memulihkan deployment sebelumnya.",
+ "warning": "Peringatan",
+ "rejectNote": "Menolak akan menghancurkan container deployment sebagian ini dan, jika ada deployment sebelumnya, memulihkannya. Catatan deployment dan log build-nya disimpan di riwayat Anda sehingga Anda dapat meninjau apa yang gagal.",
+ "keep": "Pertahankan dan Perbaiki Nanti",
+ "rejecting": "Menolak...",
+ "reject": "Tolak Deployment",
+ "retrying": "Mencoba Ulang...",
+ "retryOne": "Coba Ulang {count} Layanan Gagal",
+ "retryOther": "Coba Ulang {count} Layanan Gagal"
+ }
+ },
+ "composeServiceTally": {
+ "running": "berjalan",
+ "builtSuffix": "· {count} sudah dibangun",
+ "buildingSuffix": "· {count} sedang dibangun",
+ "failedSuffix": "· {count} gagal"
+ },
+ "composeSidebar": {
+ "status": {
+ "cancelled": "Dibatalkan",
+ "failed": "Gagal",
+ "warnings": "Peringatan",
+ "ready": "Siap",
+ "deploying": "Mendeploy"
+ },
+ "detailsTitle": "Detail Deployment",
+ "rowStatus": "Status",
+ "rowTarget": "Target",
+ "rowBuild": "Build",
+ "rowBuildTime": "Waktu Build",
+ "rowServices": "Layanan",
+ "rowProject": "Proyek",
+ "rowType": "Tipe"
+ },
+ "configDiagnostics": {
+ "title": "Sebagian openship.json repo ini tidak diterapkan",
+ "description": "Bidang yang terdaftar di bawah ini dilewati; sisa file diterapkan. Deploy tetap akan berjalan.",
+ "titleWholeFile": "openship.json repo ini diabaikan",
+ "descriptionWholeFile": "Tidak ada yang diterapkan. Deploy tetap akan berjalan, menggunakan pengaturan yang terdeteksi Openship.",
+ "refused": "Ditolak",
+ "unrecognized": "Tidak dikenali",
+ "hint": "Jalankan `openship config validate` di repo untuk memeriksa file sebelum deploy berikutnya."
+ }
+}
\ No newline at end of file
diff --git a/apps/dashboard/src/i18n/locales/id/issues.json b/apps/dashboard/src/i18n/locales/id/issues.json
new file mode 100644
index 000000000..aa770d8b7
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/id/issues.json
@@ -0,0 +1,92 @@
+{
+ "title": "Masalah",
+ "subtitle": "Semua yang ditemukan OpenShip salah di proyek, server, dan domain Anda.",
+ "tabs": {
+ "open": "Terbuka",
+ "resolved": "Selesai"
+ },
+ "resolvedNote": "Riwayat mencakup insiden container dan server — pemeriksaan lain melaporkan status saat ini, jadi sertifikat atau deployment yang diperbaiki berhenti muncul di sini.",
+ "rescan": "Pindai ulang",
+ "rescanning": "Memindai",
+ "filters": {
+ "searchPlaceholder": "Cari masalah",
+ "all": "Semua",
+ "outage": "Gangguan",
+ "action_required": "Perlu tindakan",
+ "advisory": "Saran",
+ "clear": "Hapus filter"
+ },
+ "scopes": {
+ "platform": "OpenShip",
+ "server": "Server",
+ "project": "Proyek",
+ "domain": "Domain"
+ },
+ "scopeSubtitles": {
+ "platform": "Instalasi ini sendiri",
+ "server": "Mesin tempat workload Anda berjalan",
+ "project": "Proyek yang di-deploy dan container mereka",
+ "domain": "Hostname kustom dan sertifikat"
+ },
+ "kinds": {
+ "deploy_blocked": "Deploy diblokir",
+ "prompt": "Menunggu keputusan",
+ "partial_decision": "Rilis sebagian",
+ "routing_unsynced": "Routing belum disinkronkan",
+ "domain_unverified": "Belum diverifikasi",
+ "ssl_error": "Masalah sertifikat",
+ "port_advisory": "Saran port",
+ "workload_unhealthy": "Health check gagal",
+ "workload_crash_loop": "Crash loop",
+ "workload_down": "Container mati",
+ "server_unreachable": "Server tidak terjangkau",
+ "edge_down": "Edge mati",
+ "edge_absent": "Edge belum terinstal",
+ "mail_down": "Mesin email mati",
+ "update_available": "Pembaruan tersedia",
+ "component_behind": "Pembaruan komponen tersedia"
+ },
+ "since": "Mulai {when}",
+ "resolvedWhen": "Selesai {when}",
+ "expiresIn": "Berakhir dalam {n} menit",
+ "view": "Lihat",
+ "fix": "Perbaiki",
+ "install": "Instal",
+ "update": "Perbarui",
+ "mailSetup": "Pengaturan email",
+ "selfUpdateNote": "Instalasi ini memperbarui dirinya sendiri dari command line.",
+ "empty": {
+ "openTitle": "Tidak ada yang perlu diperhatikan",
+ "openBody": "Tidak ada deploy yang diblokir atau menunggu, tidak ada masalah sertifikat atau domain, tidak ada yang tertinggal, tidak ada yang salah port. Pemeriksaan tetap berjalan di latar belakang.",
+ "resolvedTitle": "Tidak ada insiden yang selesai",
+ "resolvedBody": "Insiden container dan server muncul di sini setelah pulih, dan disimpan selama 30 hari.",
+ "filteredTitle": "Tidak ada yang cocok",
+ "filteredBody": "Beberapa masalah terbuka — tidak ada yang cocok dengan filter ini."
+ },
+ "summary": {
+ "title": "Ringkasan",
+ "subtitle": "Kesehatan sekilas",
+ "totalOpen": "terbuka",
+ "totalResolved": "selesai",
+ "bySeverity": "Berdasarkan tingkat",
+ "byArea": "Berdasarkan area"
+ },
+ "home": {
+ "attentionTitle": "Perlu perhatian",
+ "attentionSubtitle": "Ada yang rusak atau menunggu Anda",
+ "updatesTitle": "Pembaruan tersedia",
+ "updatesSubtitle": "Rilis baru siap diterapkan",
+ "more": "{n} lagi",
+ "hide": "Sembunyikan sampai ada perubahan"
+ },
+ "loadFailed": "Tidak dapat memuat masalah",
+ "toast": {
+ "title": "Masalah",
+ "rescanned": "Pindai ulang selesai",
+ "rescanPartial": "Pindai ulang selesai, {n} pemeriksaan gagal",
+ "rescanFailed": "Pindai ulang gagal",
+ "resolved": "Selesai",
+ "resolveFailed": "Tidak dapat menerapkan perbaikan",
+ "confirmDestructive": "Ini memiliki efek samping yang tidak dapat dibatalkan. Lanjutkan?"
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/id/jobs.json b/apps/dashboard/src/i18n/locales/id/jobs.json
new file mode 100644
index 000000000..49cf184a4
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/id/jobs.json
@@ -0,0 +1,186 @@
+{
+ "backups": {
+ "section": "Jadwal backup",
+ "sectionDesc": "Backup otomatis yang dijadwalkan per proyek atau layanan. Dikelola di tab Backup setiap proyek.",
+ "projectDefault": "Default proyek",
+ "mailServer": "Server email",
+ "manage": "Kelola di Backup",
+ "badge": "Backup",
+ "never": "Tidak pernah dijalankan",
+ "status": {
+ "succeeded": "Berhasil",
+ "failed": "Gagal",
+ "cancelled": "Dibatalkan",
+ "running": "Berjalan"
+ }
+ },
+ "title": "Job",
+ "subtitle": "Pemeliharaan bawaan dan command kustom yang berjalan di server Anda.",
+ "empty": "Belum ada job terjadwal.",
+ "searchPlaceholder": "Cari job…",
+ "noResults": "Tidak ada job yang cocok dengan filter Anda.",
+ "overview": {
+ "title": "Sekilas",
+ "subtitle": "Job di instance ini",
+ "all": "Semua job",
+ "running": "Berjalan",
+ "failed": "Gagal",
+ "scheduled": "Terjadwal",
+ "disabled": "Nonaktif"
+ },
+ "loadFailed": "Gagal memuat job.",
+ "kind": {
+ "system": "Sistem",
+ "custom": "Kustom"
+ },
+ "fields": {
+ "schedule": "Jadwal",
+ "nextRun": "Jalankan berikutnya",
+ "lastRun": "Terakhir dijalankan",
+ "never": "Tidak pernah dijalankan",
+ "notScheduled": "Tidak terjadwal"
+ },
+ "status": {
+ "success": "Berhasil",
+ "failed": "Gagal",
+ "running": "Berjalan"
+ },
+ "actions": {
+ "run": "Jalankan sekarang",
+ "running": "Berjalan…",
+ "edit": "Edit jadwal",
+ "save": "Simpan",
+ "cancel": "Batal",
+ "enable": "Aktifkan",
+ "disable": "Nonaktifkan",
+ "viewLogs": "Log"
+ },
+ "cronHint": "Cron 5 field standar (menit jam tanggal bulan hari), UTC.",
+ "toast": {
+ "ran": "{label} dimulai.",
+ "ranFailed": "Gagal menjalankan {label}.",
+ "saved": "Jadwal diperbarui untuk {label}.",
+ "saved2": "Job diperbarui.",
+ "saveFailed": "Gagal memperbarui {label}.",
+ "enabled": "{label} diaktifkan.",
+ "disabled": "{label} dinonaktifkan.",
+ "toggleFailed": "Gagal memperbarui {label}.",
+ "title": "Job",
+ "created": "Job dibuat.",
+ "createFailed": "Gagal membuat job.",
+ "deleted": "Job dihapus.",
+ "deleteFailed": "Gagal menghapus job."
+ },
+ "newJob": "Job baru",
+ "sections": {
+ "system": "Job sistem",
+ "systemDesc": "Pemeliharaan bawaan di instance ini.",
+ "custom": "Job kustom",
+ "customDesc": "Command yang Anda jalankan di server Anda, sesuai jadwal."
+ },
+ "customEmpty": {
+ "title": "Belum ada job kustom",
+ "desc": "Jalankan command di salah satu server Anda — sesuai jadwal, sekali, atau saat event — dengan retry, secret, log langsung, dan notifikasi.",
+ "cta": "Buat job",
+ "docs": "Dokumentasi"
+ },
+ "create": {
+ "title": "Job baru",
+ "name": "Nama",
+ "namePlaceholder": "Vakum DB malam hari",
+ "server": "Server",
+ "servers": "Server",
+ "noServers": "Belum ada server yang tersambung — tambahkan satu terlebih dahulu.",
+ "command": "Command",
+ "commandPlaceholder": "pg_dump ... > /backups/db.sql",
+ "commandHint": "Dijalankan melalui SSH di server yang dipilih. `docker run --rm image cmd` juga bisa.",
+ "scheduleType": "Kapan dijalankan",
+ "scheduleTypes": {
+ "recurring": "Sesuai jadwal",
+ "once": "Sekali",
+ "manual": "Manual"
+ },
+ "sections": {
+ "basics": "Dasar",
+ "schedule": "Jadwal",
+ "servers": "Server target",
+ "reliability": "Keandalan",
+ "environment": "Environment",
+ "triggers": "Dependensi & pemicu",
+ "notifications": "Notifikasi"
+ },
+ "schedule": "Jadwal (cron)",
+ "runAt": "Jalankan pada",
+ "manualHint": "Hanya dijalankan saat dipicu — Jalankan Sekarang, dependensi, atau event.",
+ "timeout": "Batas waktu (dtk)",
+ "retryAttempts": "Percobaan maks",
+ "retryBackoff": "Backoff (dtk)",
+ "advanced": "Lanjutan",
+ "env": "Environment variable",
+ "addVar": "Tambah variabel",
+ "secrets": "Secret",
+ "addSecret": "Tambah secret",
+ "replaceSecrets": "Ganti secret",
+ "secretsSet": "secret ditetapkan",
+ "noSecrets": "Belum ada secret.",
+ "dependencies": "Jalankan setelah",
+ "dependenciesHint": "Job ini dijalankan setelah job yang dipilih berhasil.",
+ "triggers": "Pemicu event",
+ "notifications": "Notifikasi",
+ "noChannels": "Belum ada channel notifikasi — tambahkan di Pengaturan → Notifikasi.",
+ "notificationsHint": "Mengganti pengaturan notifikasi global untuk job ini.",
+ "submit": "Buat job",
+ "creating": "Menyimpan…",
+ "cancel": "Batal",
+ "needServer": "Hubungkan server terlebih dahulu",
+ "pasteEnv": "Tempel .env",
+ "uploadEnv": "Unggah .env",
+ "envPasteEmpty": "Tidak ada variabel yang ditemukan",
+ "comingSoon": "Segera",
+ "setupChannel": "Atur channel",
+ "customTriggerSoon": "Pemicu kustom",
+ "docsLink": "Pelajari tentang job",
+ "docsBody": "Jadwal, retry, secret & notifikasi",
+ "summary": {
+ "title": "Ringkasan",
+ "command": "Command",
+ "schedule": "Jadwal",
+ "targets": "Target",
+ "retry": "Retry",
+ "timeout": "Batas waktu",
+ "notify": "Notifikasi",
+ "none": "—",
+ "retryOff": "Tidak ada"
+ }
+ },
+ "edit": {
+ "title": "Edit job",
+ "action": "Edit",
+ "save": "Simpan perubahan"
+ },
+ "detail": {
+ "back": "Kembali ke job",
+ "tabs": {
+ "overview": "Ringkasan",
+ "runs": "Jalankan"
+ },
+ "servers": "Server",
+ "retry": "Retry",
+ "retryValue": "{n} percobaan, backoff {s}dtk",
+ "notifyValue": "{c} channel pada {s}",
+ "onceAt": "Sekali pada",
+ "noRuns": "Belum ada jalankan."
+ },
+ "delete": {
+ "action": "Hapus",
+ "confirm": "Hapus job ini?",
+ "deleting": "Menghapus…"
+ },
+ "logs": {
+ "title": "Log jalankan",
+ "streaming": "Streaming…",
+ "empty": "Belum ada output.",
+ "close": "Tutup",
+ "exit": "Kode keluar {code}"
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/id/library.json b/apps/dashboard/src/i18n/locales/id/library.json
new file mode 100644
index 000000000..c54fea2c9
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/id/library.json
@@ -0,0 +1,187 @@
+{
+ "ghCliConsent": {
+ "title": "Baca repository Anda?",
+ "body": "OpenShip dapat mendaftar repository GitHub Anda menggunakan gh CLI yang sudah Anda masuki.",
+ "bodyWithLogin": "OpenShip dapat mendaftar repository GitHub Anda menggunakan gh CLI yang Anda masuki sebagai @{login}.",
+ "pointNoCloud": "Tidak ada yang dikirim ke cloud — daftar tidak pernah meninggalkan mesin ini.",
+ "pointDesktopOnly": "Hanya berjalan di aplikasi desktop, menggunakan auth gh lokal Anda.",
+ "pointReadOnly": "Hanya-baca: mendaftar repo sehingga Anda bisa memilih satu untuk deploy.",
+ "allow": "Izinkan"
+ },
+ "page": {
+ "title": "Proyek Baru",
+ "subtitle": "Impor repository, tempel URL, atau mulai dari template",
+ "tabs": {
+ "folder": "Folder",
+ "github": "GitHub",
+ "url": "Git URL",
+ "template": "Template"
+ }
+ },
+ "connect": {
+ "connectGithub": "Hubungkan GitHub",
+ "manageInSettings": "Metode koneksi lain",
+ "terminal": {
+ "title": "Hubungkan GitHub",
+ "checkConnection": "Periksa Koneksi",
+ "afterLogin": "Setelah masuk, klik \"Periksa Koneksi\" untuk melanjutkan."
+ },
+ "deviceFlow": {
+ "title": "Masuk ke GitHub",
+ "goToPrefix": "Buka",
+ "goToSuffix": "Masukkan kode ini di GitHub untuk menyelesaikan masuk.",
+ "waiting": "Menunggu otorisasi…",
+ "openGithub": "Buka GitHub",
+ "copyCode": "Klik untuk menyalin"
+ },
+ "default": {
+ "title": "Hubungkan GitHub untuk deploy",
+ "descSelfHosted": "Pilih cara OpenShip mengakses repository Anda.",
+ "descSaas": "Tautkan akun GitHub Anda untuk menjelajahi repo dan deploy setiap push.",
+ "recommended": "Direkomendasikan",
+ "cloudApp": "Aplikasi GitHub OpenShip Cloud",
+ "cloudAppDesc": "Aplikasi GitHub terkelola — deploy ke server remote dengan token jangka pendek.",
+ "connectCloud": "Hubungkan OpenShip Cloud",
+ "manual": "Koneksi GitHub manual",
+ "manualDesc": "Atur di Pengaturan — GitHub App/OAuth Anda sendiri, atau gh CLI instance ini. Tanpa akun OpenShip.",
+ "manualCta": "Atur di Pengaturan",
+ "localBuilds": "Build lokal",
+ "ghCli": "Masuk dengan GitHub",
+ "ghCliDesc": "Masuk browser dengan kode perangkat. Tanpa pengaturan aplikasi, tanpa SSH, tanpa akun OpenShip.",
+ "useGhCli": "Masuk",
+ "connecting": "Menghubungkan…",
+ "byoc": "SSH key atau access token",
+ "byocDesc": "Bawa credential Anda sendiri — deploy key per server, atau token. Tanpa masuk.",
+ "byocCta": "Atur"
+ },
+ "token": {
+ "title": "Hubungkan dengan token GitHub",
+ "connect": "Hubungkan",
+ "create": "Buat token di GitHub",
+ "failed": "Tidak dapat menyimpan token."
+ }
+ },
+ "folderUpload": {
+ "emptyFolder": "Folder itu tampak kosong (atau semuanya diabaikan). Pilih folder proyek.",
+ "uploadError": "Gagal mengunggah folder",
+ "stackTitle": "Apa yang Anda deploy?",
+ "stackDesc": "Pilih stack untuk folder Anda — ini mengatur lingkungan build. Anda akan mengunggahnya selanjutnya.",
+ "backToStack": "Kembali ke pemilihan stack",
+ "uploadTitle": "Unggah folder {stack} Anda",
+ "uploadSubtitle": "Dikemas di browser Anda — node_modules, .git, dan output build dilewati",
+ "dropTitle": "Jatuhkan folder proyek Anda atau klik untuk menjelajahi",
+ "dropSubtitle": "Seluruh folder diunggah dan dibangun sebagai aplikasi {stack}",
+ "filesSingular": "{count} file",
+ "filesPlural": "{count} file",
+ "clearSelection": "Hapus pilihan",
+ "packing": "Membungkus folder…",
+ "uploading": "Mengunggah…",
+ "continue": "Lanjutkan"
+ },
+ "localProjects": {
+ "title": "Proyek Lokal",
+ "loading": "Memuat…",
+ "projectCountSingular": "{count} proyek",
+ "projectCountPlural": "{count} proyek",
+ "import": "Impor",
+ "empty": {
+ "title": "Belum ada proyek lokal",
+ "description": "Impor folder proyek dari mesin Anda untuk memindai, membangun, dan menjalankannya secara lokal",
+ "importProject": "Impor Proyek",
+ "howItWorks": "Cara kerja",
+ "dropTitle": "Jatuhkan atau jelajahi",
+ "dropDesc": "Pilih folder proyek apa pun",
+ "autoDetectTitle": "Deteksi otomatis",
+ "autoDetectDesc": "Framework & konfigurasi ditemukan",
+ "deployTitle": "Jalankan secara lokal",
+ "deployDesc": "Bangun & jalankan di mesin Anda"
+ },
+ "form": {
+ "title": "Impor dari folder",
+ "scanErrorDefault": "Gagal memindai direktori",
+ "dropTitle": "Jatuhkan folder di sini atau klik untuk menjelajahi",
+ "dropSubtitle": "Pilih folder proyek Anda untuk mendeteksi framework secara otomatis",
+ "folderViaBrowser": "Folder dipilih melalui browser — masukkan path absolut lengkap dan tekan Pindai",
+ "folderDropped": "Folder dijatuhkan — masukkan path absolut lengkap dan tekan Pindai",
+ "pathPlaceholder": "/path/to/your/project",
+ "browse": "Jelajahi",
+ "scan": "Pindai",
+ "importError": "Gagal mengimpor proyek",
+ "projectName": "Nama proyek",
+ "detected": "Terdeteksi",
+ "build": "Build:",
+ "install": "Instal:",
+ "output": "Output:",
+ "start": "Mulai:",
+ "importing": "Mengimpor…",
+ "importProject": "Impor Proyek"
+ }
+ },
+ "repositoryList": {
+ "searchPlaceholder": "Cari repository…",
+ "visibility": {
+ "all": "Semua",
+ "public": "Publik",
+ "private": "Privat"
+ },
+ "sort": {
+ "recent": "Terbaru",
+ "name": "Nama",
+ "stars": "Bintang"
+ },
+ "addAccount": "Tambah akun GitHub",
+ "installTitle": "Instal Aplikasi GitHub",
+ "installDesc": "Instal Aplikasi GitHub di akun atau organisasi Anda untuk memberikan akses ke repository Anda.",
+ "installButton": "Instal Aplikasi GitHub",
+ "noMatching": "Tidak ada repository yang cocok",
+ "noRepos": "Tidak ada repository yang ditemukan",
+ "noMatchingDesc": "Coba istilah pencarian yang berbeda atau sesuaikan filter Anda",
+ "noReposDesc": "Akun ini belum memiliki repository",
+ "privateBadge": "Privat",
+ "localOnly": "Hanya lokal",
+ "localOnlyTooltip": "Aplikasi OpenShip belum terinstal di {owner}. Build lokal saja — instal Aplikasi di owner ini untuk mengaktifkan deploy remote.",
+ "repoCountSingular": "{count} repository",
+ "repoCountPlural": "{count} repository",
+ "matching": "cocok dengan \"{query}\"",
+ "pagination": {
+ "previous": "Sebelumnya",
+ "next": "Berikutnya",
+ "pageOf": "Halaman {page} dari {total}"
+ },
+ "time": {
+ "justNow": "baru saja",
+ "minutesAgo": "{n}m lalu",
+ "hoursAgo": "{n}j lalu",
+ "daysAgo": "{n}h lalu",
+ "monthsAgo": "{n}bln lalu"
+ }
+ },
+ "sidebar": {
+ "overview": "Ringkasan",
+ "total": "Total",
+ "public": "Publik",
+ "private": "Privat",
+ "quickTip": "Tips Cepat",
+ "quickTipDesc": "Pilih repository apa pun untuk langsung deploy. Konfigurasi deployment otomatis setiap push.",
+ "connection": "Koneksi",
+ "openshipGithubApp": "Aplikasi GitHub OpenShip",
+ "connected": "Tersambung",
+ "notConnected": "Belum tersambung",
+ "ghCli": "gh CLI",
+ "runGhAuth": "Jalankan `gh auth login`",
+ "openshipCloudApp": "Aplikasi OpenShip Cloud",
+ "checking": "Memeriksa…",
+ "notConnectedManage": "Belum tersambung — kelola di Pengaturan",
+ "optional": "Opsional",
+ "footnote": "Pustaka lokal melalui gh CLI; deploy remote menggunakan token jangka pendek —",
+ "manageGithubSettings": "kelola GitHub di Pengaturan",
+ "methodToken": "Token GitHub",
+ "methodDevice": "Masuk GitHub"
+ },
+ "urlImport": {
+ "invalidUrl": "Masukkan URL repository GitHub yang valid",
+ "title": "Impor dari Git URL",
+ "description": "Tempel URL repository publik untuk mengimpor. Tanpa koneksi GitHub yang diperlukan.",
+ "importButton": "Impor Repository"
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/id/migration.json b/apps/dashboard/src/i18n/locales/id/migration.json
new file mode 100644
index 000000000..cb9248917
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/id/migration.json
@@ -0,0 +1,261 @@
+{
+ "wizard": {
+ "title": "Migrasi aplikasi",
+ "intro": "Periksa aplikasi yang berjalan di server ini dan adopsi ke Openship. Ini hanya baca — tidak ada yang berubah di server sampai Anda deploy.",
+ "selectServer": "Pilih server",
+ "scan": "Pindai server",
+ "scanning": "Memindai…",
+ "rescan": "Pindai Ulang",
+ "flatDocker": "Daftar datar",
+ "flatDockerHint": "Abaikan label Openship — adopsi semuanya (termasuk container yang dikelola Openship) sebagai Docker biasa.",
+ "flatDockerShort": "Adopsi juga container yang dikelola Openship.",
+ "cancel": "Batal",
+ "close": "Tutup",
+ "projectName": "Nama proyek",
+ "projectNamePlaceholder": "my-app",
+ "create": "Buat proyek",
+ "creating": "Membuat…",
+ "createdToast": "Proyek dibuat dari {count} layanan",
+ "migrate": "Migrasi",
+ "migrateN": "Migrasi {n} proyek",
+ "migrating": "Migrasi…",
+ "addProject": "Tambah proyek",
+ "removeProject": "Hapus proyek",
+ "targetLabel": "Server target",
+ "sameServer": "Server yang sama (gunakan kembali volume di tempat)",
+ "crossServerBuiltInfo": "Gambar yang dibangun secara lokal dialirkan ke target sebagai data (tanpa registry, tanpa rebuild). Gambar yang besar memakan waktu lebih lama — bilah kemajuan menampilkan transfer.",
+ "killOriginals": "Hentikan & hapus container lama setelah deployment baru terverifikasi",
+ "transfer": {
+ "label": "Transfer",
+ "default": "Server-ke-server langsung (tercepat)",
+ "auto": "Otomatis",
+ "stream": "Relay melalui host kontrol (cadangan)",
+ "direct": "Langsung",
+ "rsync": "rsync"
+ },
+ "plan": {
+ "title": "Rencana transfer",
+ "total": "total",
+ "volume": "vol",
+ "bind": "dir",
+ "image": "gambar",
+ "path": "path",
+ "unknown": "ukuran tidak diketahui",
+ "empty": "Tidak ada yang ditransfer.",
+ "compress": "Kompres (rsync -z)",
+ "sslTitle": "Sertifikat SSL",
+ "sslReuse": "sertifikat ditemukan, akan digunakan kembali",
+ "sslIssue": "tidak ada sertifikat — diterbitkan saat publish",
+ "pathsTitle": "Path tambahan",
+ "pathAdd": "Tambah",
+ "pathRemove": "Hapus path",
+ "pathSrcPlaceholder": "/path/sumber di server lama",
+ "pathDestPlaceholder": "/path/tujuan di server baru",
+ "conflictTitle": "Target sudah memiliki data",
+ "conflictDesc": "Volume layanan ini sudah ada di target. Pilih apa yang harus dilakukan dengan masing-masing:",
+ "conflictOverride": "Timpa",
+ "conflictOverrideHint": "Timpa dengan sumber",
+ "conflictClone": "Kloning",
+ "conflictCloneHint": "Volume baru, pertahankan yang lama",
+ "conflictKeep": "Pertahankan",
+ "conflictKeepHint": "Gunakan data yang ada",
+ "measuring": "Mengukur ukuran transfer…"
+ },
+ "steps": {
+ "select": "Pilih",
+ "source": "Sumber",
+ "domains": "Konfigurasi",
+ "next": "Selanjutnya",
+ "back": "Kembali",
+ "sourceHint": "Secara opsional hubungkan repo dan petakan container ke layanan compose-nya — gambar yang berjalan tetap digunakan kembali.",
+ "linkRepo": "Hubungkan repository",
+ "linkRepoDesc": "Hubungkan repo agar Openship mengetahui sumber setiap layanan (auto-deploy + rebuild nanti). Migrasi ini tetap menggunakan gambar yang berjalan — tidak rebuild sekarang.",
+ "repoOptional": "opsional",
+ "connectGithub": "Hubungkan GitHub",
+ "selectRepo": "Pilih repository",
+ "repoUrlPlaceholder": "atau tempel github.com/pemilik/repo",
+ "repoUrlInvalid": "Masukkan repo GitHub sebagai pemilik/repo atau URL github.com.",
+ "repoUrlAdd": "Tambah",
+ "branch": "Branch",
+ "unlinkRepo": "Hapus",
+ "mapTitle": "Petakan container ke layanan compose",
+ "mapHint": "Tunjuk setiap container yang ditemukan ke layanan di compose repo. Build path layanan yang cocok menjadi sumbernya.",
+ "mapToService": "Pilih layanan",
+ "notInRepo": "Tidak ada di repo",
+ "composeServicesTitle": "Layanan repo",
+ "noComposeFound": "Tidak ada docker-compose yang ditemukan di repo ini — tautan repo berada di tingkat proyek.",
+ "mapNoRepo": "Hubungkan repository di langkah Pilih untuk memetakan container ke layanan compose-nya (opsional).",
+ "mapSkipHint": "Menghubungkan repo bersifat opsional — lewati untuk melanjutkan (gambar yang berjalan digunakan kembali apa adanya).",
+ "mapField": "Layanan repo",
+ "repoConnectHint": "Hubungkan GitHub di panel untuk menjelajahi repository Anda — atau tempel URL repo.",
+ "repoPasteHint": "Atau tempel URL repository.",
+ "repoOnSourceHint": "Hubungkan repository Git di langkah berikutnya (Sumber) — opsional.",
+ "parsingCompose": "Mengurai compose…",
+ "envTitle": "Variabel lingkungan",
+ "publishDomains": "Publikasikan domain",
+ "routeTitle": "Route",
+ "routeKeep": "Pertahankan",
+ "routeFree": "Subdomain",
+ "routeCustom": "Kustom",
+ "routeNone": "Tidak ada",
+ "sslOn": "SSL",
+ "sslOff": "Tanpa SSL",
+ "internalOnly": "Hanya internal — tidak terekspos secara publik.",
+ "serviceNewBadge": "Baru",
+ "deployActionReuse": "Gunakan kembali gambar",
+ "deployActionBuild": "Akan dibangun",
+ "deployActionPull": "Akan ditarik"
+ }
+ },
+ "run": {
+ "title": "Migrasi stack Anda",
+ "queued": "Dalam antrean…",
+ "adopting": "Membuat proyek Openship…",
+ "moving_data": "Memindahkan data volume ke target…",
+ "deploying": "Mendeploy di server target…",
+ "verifying": "Memverifikasi deployment baru…",
+ "awaiting_cutover": "Deployment baru sudah live — verifikasi, lalu selesaikan",
+ "cutover": "Menghapus container asli…",
+ "succeeded": "Migrasi selesai",
+ "allSucceeded": "{n} proyek dimigrasi",
+ "queueHeader": "Proyek {index} dari {total} · {name}",
+ "failed": "Migrasi gagal",
+ "rolled_back": "Dikembalikan — container asli Anda di-restart",
+ "openProject": "Buka proyek",
+ "addDomains": "Tambah domain",
+ "viewDeployLogs": "Lihat log deploy",
+ "deployDetail": "Log deploy",
+ "routeHint": "Layanan Anda sudah diimpor tetapi belum publik. Tambahkan domain per layanan di tab Domain — Openship mendeploy secara otomatis untuk menginstal edge-nya (OpenResty) dan, jika proxy lama Anda masih menahan 80/443, menawarkan untuk mengambil alih.",
+ "downtimeNote": "Container asli dihentikan selama pemindahan; harapkan downtime singkat sampai deployment baru go live.",
+ "movingImage": "gambar",
+ "movingVolume": "volume",
+ "partial": "Sebagian — beberapa path tidak berpindah"
+ },
+ "cutover": {
+ "title": "Selesaikan migrasi",
+ "warning": "Deployment baru sudah live di server target. Anda dapat menghentikan & menghapus container asli sekarang, atau mempertahankannya sebagai jaring pengaman dan menghapusnya nanti — di server target yang berbeda mereka di-restart sebagai cadangan live; di server yang sama mereka tetap berhenti (stack baru sekarang memegang port mereka).",
+ "stopRemove": "Hentikan & hapus yang asli",
+ "keep": "Pertahankan yang asli",
+ "working": "Bekerja…"
+ },
+ "discover": {
+ "buildBlocked": "Dibangun dari sumber — belum dapat dimigrasi (publish gambar terlebih dahulu)",
+ "proxyExcluded": "Reverse proxy ({ports}) — tidak diimpor; edge Openship menggantikannya.",
+ "edgePortReserved": "Port {ports} dicadangkan untuk edge Openship; tidak diterbitkan.",
+ "nothing": "Tidak ada container Docker yang dapat diadopsi ditemukan di server ini.",
+ "servicesTitle": "Layanan",
+ "volumesTitle": "Volume",
+ "networksTitle": "Jaringan",
+ "warningsTitle": "Sebelum Anda migrasi",
+ "composeProjects": "Proyek compose",
+ "composeGroup": "Compose",
+ "standaloneGroup": "Container independen",
+ "standalone": "independen",
+ "selectAll": "Pilih semua",
+ "selectedCount": "{n} dari {total} dipilih",
+ "claimedIn": "di {project}",
+ "otherComposeHint": "Bagian dari {group} — tambahkan proyek terpisah untuk mengimpornya",
+ "emptyProject": "Belum ada layanan yang dipilih — pilih dari sebelah kiri.",
+ "running": "berjalan",
+ "stopped": "berhenti",
+ "image": "Gambar",
+ "build": "Build",
+ "ports": "Port",
+ "env": "Env",
+ "volumes": "Volume",
+ "dependsOn": "Bergantung pada",
+ "inUseBy": "digunakan oleh",
+ "reused": "digunakan kembali di tempat",
+ "volumeReuse": "Ambil alih",
+ "volumeCopy": "Salin",
+ "volumeCopyHint": "Ambil alih menggunakan kembali volume yang ada di tempat. Salin menduplikatnya ke volume Openship baru dan membiarkan yang asli tidak tersentuh (menggunakan disk ekstra).",
+ "bind": "path host",
+ "nVolumes": "{n} volume",
+ "nEnv": "{n} env vars",
+ "envFromImage": "{n} variabel berasal dari gambar",
+ "envFromImageHint": "Tidak diimpor sebagai config — gambar menyediakan ini saat runtime. Impor untuk menetapkan nilai sebelum Anda rebuild dari sumber.",
+ "envFromImageImport": "Impor tetap",
+ "alreadyManaged": "{count} container yang sudah dikelola Openship dilewati."
+ },
+ "reimport": {
+ "title": "Proyek Openship ditemukan",
+ "intro": "Proyek Openship ini berjalan di server ini tetapi tidak ada di instance ini (database yang di-reset, atau server dari Openship lain). Impor ulang untuk memulihkannya — ID asli dipertahankan sehingga container yang berjalan dapat menempel kembali.",
+ "services": "{n} layanan",
+ "lastSeen": "Terakhir di-deploy {when}",
+ "fullRestore": "Pemulihan penuh",
+ "bestEffort": "Pemulihan upaya terbaik",
+ "action": "Impor ulang",
+ "working": "Mengimpor ulang…",
+ "reimported": "Diimpor ulang",
+ "openProject": "Buka proyek",
+ "alreadyManaged": "{n} container sudah dikelola oleh instance ini.",
+ "finalizeNote": "Impor ulang memulihkan proyek dan menempelkan kembali container yang berjalan secara live — tidak perlu redeploy. Redeploy hanya untuk mengubah build.",
+ "failed": "Impor ulang gagal"
+ },
+ "scanFailed": "Inspeksi Docker gagal",
+ "adoptFailed": "Tidak dapat membuat proyek",
+ "entry": {
+ "cardTitle": "Layanan yang sudah ada",
+ "cardDesc": "Migrasi deployment Docker atau Compose yang berjalan ke Openship.",
+ "action": "Migrasi aplikasi",
+ "tab": "Server yang sudah ada",
+ "comingSoon": "Migrasi Docker segera hadir"
+ },
+ "view": {
+ "tabView": "Lihat layanan",
+ "tabMigrate": "Migrasi",
+ "adoptedGroup": "Dikelola oleh {project}",
+ "openProject": "Buka proyek",
+ "migrateToManage": "Migrasi untuk mengelola",
+ "back": "Kembali ke layanan"
+ },
+ "tab": {
+ "new": "Migrasi baru",
+ "empty": "Belum ada migrasi",
+ "emptyDesc": "Pindahkan stack Docker atau Compose yang berjalan dari server ini ke Openship, atau bawa dari server lain.",
+ "close": "Tutup",
+ "sessionLog": "Log sesi",
+ "noLogs": "Belum ada output log.",
+ "detailTitle": "Migrasi",
+ "inPlace": "Di tempat",
+ "crossServer": "Lintas server",
+ "servicesCount": "{n} layanan",
+ "cancelRun": "Batalkan migrasi",
+ "confirmCancel": "Batalkan migrasi ini? Transfer berhenti dan perubahan apa pun di target dikembalikan.",
+ "inFlight": "Sedang berlangsung",
+ "status": {
+ "queued": "Dalam antrean",
+ "adopting": "Mengadopsi",
+ "moving_data": "Memindahkan data",
+ "deploying": "Mendeploy",
+ "verifying": "Memverifikasi",
+ "awaiting_cutover": "Menunggu cutover",
+ "cutover": "Melakukan cutover",
+ "succeeded": "Berhasil",
+ "failed": "Gagal",
+ "rolled_back": "Dikembalikan",
+ "partial": "Sebagian"
+ },
+ "delete": "Hapus catatan",
+ "confirmDelete": "Hapus catatan migrasi ini? Log dihapus — proyek dan datanya tidak tersentuh.",
+ "back": "Kembali ke migrasi",
+ "pendingTitle": "Path yang tidak berpindah",
+ "pendingMissing": "tidak ditemukan di sumber",
+ "pendingError": "transfer gagal",
+ "editPath": "Edit path sumber",
+ "overridePlaceholder": "Path sumber baru (kosong = coba ulang apa adanya)",
+ "skip": "Lewati",
+ "undoSkip": "Pertahankan",
+ "resume": "Lanjutkan migrasi",
+ "resuming": "Melanjutkan…",
+ "editRetry": "Edit & coba ulang",
+ "safetyTitle": "Non-destruktif & dapat dibatalkan.",
+ "safetyBody": "Data disalin, tidak pernah dipindah — sumber Anda hanya dijeda sebentar, dan tidak ada yang dihapus kecuali Anda secara eksplisit melakukan cutover.",
+ "cleanupTarget": "Hapus data yang disalin dari target",
+ "retryRun": "Coba Ulang",
+ "changeTarget": "Ubah target"
+ },
+ "plan": {
+ "missing": "tidak ditemukan"
+ }
+}
\ No newline at end of file
diff --git a/apps/dashboard/src/i18n/locales/id/misc.json b/apps/dashboard/src/i18n/locales/id/misc.json
new file mode 100644
index 000000000..4570cb97b
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/id/misc.json
@@ -0,0 +1,372 @@
+{
+ "authorize": {
+ "invalidRequest": "Permintaan Tidak Valid",
+ "missingParams": "Parameter otorisasi tidak ada.",
+ "title": "Otorisasi {app}",
+ "wantsToConnect": "{app} ingin menghubungkan ke akun Anda.",
+ "wantsToConnectOnMachine": "{app} di {machine} ingin menghubungkan ke akun Anda.",
+ "signedInAs": "Masuk sebagai",
+ "willAllow": "Ini akan mengizinkan:",
+ "permDeploy": "Deploy dan mengelola aplikasi",
+ "permAccess": "Mengakses proyek dan pengaturan Anda",
+ "authorize": "Otorisasi",
+ "cancel": "Batal"
+ },
+ "mcpAuthorize": {
+ "title": "Otorisasi MCP client",
+ "subtitle": "Sebuah MCP client ingin menghubungkan ke akun Openship Anda.",
+ "signedInAs": "Masuk sebagai",
+ "client": "Client",
+ "organization": "Organisasi",
+ "orgScopeNote": "Client hanya dapat bertindak dalam organisasi ini.",
+ "switchOrgError": "Tidak dapat mengganti organisasi. Silakan coba lagi.",
+ "thisOrganization": "organisasi ini",
+ "missingClientId": "client_id tidak ada — tautan otorisasi ini tidak valid.",
+ "signInRequiredTitle": "Masuk untuk melanjutkan",
+ "signInRequiredBody": "Kami tidak dapat mengonfirmasi sesi Anda di halaman ini, jadi client tidak diotorisasi. Masuk lagi dan permintaan akan dilanjutkan.",
+ "signInAction": "Masuk",
+ "templatesHeading": "Pilih apa yang dapat dilakukan client ini",
+ "templatesNote": "Anda hanya dapat memberikan akses yang Anda miliki sendiri.",
+ "recommendedBadge": "Direkomendasikan",
+ "broadBadge": "Luas",
+ "editedBadge": "Diedit",
+ "resetTemplate": "Atur Ulang",
+ "tplAgentTitle": "Coding agent",
+ "tplAgentDesc": "Membuat dan deploy proyek baru. Tidak dapat melihat yang sudah ada.",
+ "tplExistingTitle": "Proyek yang ada",
+ "tplExistingDesc": "Deploy, restart, dan baca log untuk proyek yang Anda pilih. Tidak ada yang lain.",
+ "tplReadOnlyTitle": "Read-only",
+ "tplReadOnlyDesc": "Membaca apa pun yang dapat Anda baca di {org}. Semua tulis diblokir.",
+ "tplFullTitle": "Akses penuh akun",
+ "tplFullDesc": "Bertindak sebagai Anda di {org} — setiap proyek, server, dan alat. Kepenuhan kepercayaan saja.",
+ "tplCustomTitle": "Kustom",
+ "tplCustomDesc": "Kombinasi sumber daya Anda sendiri.",
+ "editorHeading": "Sesuaikan akses",
+ "editorNote": "Perubahan muncul di ringkasan segera.",
+ "capabilitiesHeading": "Kemampuan",
+ "createProjectsTitle": "Membuat proyek baru",
+ "createProjectsDesc": "Client dapat membuat proyek dan mengelola yang dibuatnya — termasuk menghapusnya. Tidak dapat melihat proyek yang tidak dibuatnya.",
+ "resourcesHeading": "Sumber Daya",
+ "advancedNote": "Ini tidak membuka MCP tool, tetapi otorisasi yang sama masih dapat mencapainya melalui REST API.",
+ "summaryHeading": "Apa yang akan didapatkan client ini",
+ "summaryEmptyTitle": "Belum ada yang diberikan",
+ "summaryEmptyBody": "Pilih sumber daya di sebelah kiri, atau pilih template di atas.",
+ "summaryUnscopedTitle": "Semua yang dapat Anda akses di {org}",
+ "summaryUnscopedBody": "Client bertindak dengan peran Anda sendiri. Tidak pernah lebih sempit dari Anda.",
+ "summaryReadOnlyTitle": "Read-only di {org}",
+ "summaryReadOnlyBody": "Client dapat membaca apa pun yang dapat Anda baca, dan tidak menulis.",
+ "capabilityRowTitle": "Membuat proyek baru",
+ "capabilityRowDesc": "dan mengelola penuh yang dibuatnya",
+ "levelView": "Lihat",
+ "levelManage": "Deploy & kelola",
+ "levelFull": "Kontrol penuh",
+ "removeGrant": "Hapus",
+ "wildcardTag": "Semua",
+ "grantCountOne": "{count} sumber daya",
+ "grantCountOther": "{count} sumber daya",
+ "canHeading": "Akan dapat",
+ "cannotHeading": "Tidak akan dapat",
+ "dgCreateProjects": "Membuat proyek baru di {org}",
+ "dgManageOwnProjects": "Mengelola penuh proyek yang dibuatnya, termasuk menghapusnya",
+ "dgOperatePickedProjects": "Deploy, restart, dan mengubah proyek yang tercantum di atas",
+ "dgViewPickedProjects": "Membaca proyek yang tercantum di atas — config, log, dan status",
+ "dgReadGithub": "Melihat repository GitHub Anda dan mendeteksi cara membangunnya",
+ "dgWriteGithub": "Melihat dan mengubah repository GitHub Anda",
+ "dgServers": "Mencapai server yang tercantum di atas melalui API",
+ "dgBackups": "Mencapai tujuan backup yang tercantum di atas",
+ "dgEverything": "M melakukan apa pun yang dapat Anda lakukan di {org}",
+ "dgReadEverything": "Membaca apa pun yang dapat Anda baca di {org}",
+ "dgNotExistingProjects": "Melihat atau mengubah proyek yang tidak dibuatnya",
+ "dgNotEnumerateProjects": "Mendaftar proyek Anda — hanya dapat mencapai yang disebutkan di atas",
+ "dgNotServers": "Menyentuh server, email, atau pengaturan organisasi Anda",
+ "dgNotDelete": "Menghapus apa pun",
+ "dgNoWrites": "Deploy, mengubah, atau menghapus apa pun",
+ "readOnly": "Read-only",
+ "readOnlyDesc": "Blokir semua tulis, apa pun yang tercantum di atas.",
+ "readOnlySecretsNote": "Read-only tetap mengekspos secret yang dapat dicapai client, termasuk nilai variabel environment dan string koneksi.",
+ "readOnlyConflictCreate": "Read-only memblokir pembuatan proyek, jadi \"Membuat proyek baru\" tidak akan melakukan apa pun.",
+ "readOnlyConflictWritesOne": "Read-only memblokir tulis, jadi akses tulis pada {count} sumber daya tidak akan melakukan apa pun.",
+ "readOnlyConflictWritesOther": "Read-only memblokir tulis, jadi akses tulis pada {count} sumber daya tidak akan melakukan apa pun.",
+ "turnOffReadOnly": "Matikan read-only",
+ "blockedNoResources": "Ini tidak akan memberikan apa pun. Pilih setidaknya satu sumber daya, atau pilih template.",
+ "blockedNoPermissions": "Setiap sumber daya telah mengosongkan aksesnya. Atur level, atau hapus baris.",
+ "authorizeError": "Tidak dapat menyelesaikan otorisasi. Silakan coba lagi.",
+ "grantRejectedTitle": "Akun Anda tidak dapat memberikan itu",
+ "grantRejected": "{resource} dihapus karena akses Anda sendiri tidak mencakupnya. Tinjau ringkasan dan otorisasi lagi.",
+ "deny": "Tolak",
+ "authorize": "Otorisasi",
+ "dgReadSource": "Membaca isi file dari path yang Anda tentukan",
+ "dgNotReadSource": "Membaca isi file dari repository Anda",
+ "levelViewHint": "Hanya baca: pengaturan, log, dan metadata. Tidak ada perubahan.",
+ "levelManageHint": "Deploy, restart, dan mengubah pengaturan. Tidak dapat menghapus.",
+ "levelFullHint": "Semua, termasuk menghapus sumber daya itu sendiri."
+ },
+ "acceptInvite": {
+ "invalidInvitation": "Undangan tidak valid",
+ "invitationStatus": "Undangan ini {status}.",
+ "wrongAccount": "Undangan ini untuk {email}. Anda masuk sebagai {currentEmail}.",
+ "loadFailed": "Gagal memuat undangan",
+ "acceptFailed": "Gagal menerima undangan",
+ "theOrganization": "organisasi",
+ "invitedTitle": "Anda diundang",
+ "needsLoginPre": "Bergabung ",
+ "needsLoginMid": " di Openship. Masuk atau buat akun dengan ",
+ "needsLoginPost": " untuk menerima.",
+ "signIn": "Masuk",
+ "createAccount": "Buat akun",
+ "joinTitle": "Bergabung dengan {org}",
+ "readyPre": "Anda telah diundang untuk bergabung dengan ",
+ "readyMid": " sebagai ",
+ "readyPost": ".",
+ "decline": "Tolak",
+ "accept": "Terima",
+ "joining": "Bergabung dengan organisasi...",
+ "acceptedTitle": "Anda sudah masuk",
+ "acceptedRedirect": "Mengalihkan ke dashboard...",
+ "errorTitle": "Tidak dapat menerima undangan ini",
+ "backToDashboard": "Kembali ke dashboard"
+ },
+ "cloudAuthorize": {
+ "missingRedirect": "Parameter redirect tidak ada.",
+ "missingState": "Parameter state tidak ada.",
+ "missingCodeChallenge": "Parameter code_challenge tidak ada.",
+ "invalidState": "Parameter state tidak valid.",
+ "invalidCodeChallenge": "Parameter code_challenge tidak valid.",
+ "invalidRedirectUrl": "URL redirect tidak valid.",
+ "redirectProtocol": "Redirect harus menggunakan http atau https.",
+ "redirectHttps": "Redirect non-localhost harus menggunakan HTTPS.",
+ "redirectUserinfo": "URL redirect tidak boleh berisi userinfo.",
+ "authorizeError": "Tidak dapat mengotorisasi koneksi ini.",
+ "invalidTitle": "Permintaan otorisasi tidak valid",
+ "title": "Otorisasi perangkat",
+ "instancePre": "Instance Openship di ",
+ "instancePost": " ingin menghubungkan ke akun Openship Cloud Anda.",
+ "signedInAs": "Masuk sebagai",
+ "authorizing": "Mengotorisasi…",
+ "authorize": "Otorisasi",
+ "cancel": "Batal",
+ "deviceDoneTitle": "Terhubung ke Openship Cloud",
+ "deviceDoneBody": "Anda dapat menutup tab ini dan kembali ke terminal — pengaturan akan dilanjutkan secara otomatis."
+ },
+ "cloudConnect": {
+ "noCode": "Kode autentikasi tidak disediakan. Silakan coba lagi.",
+ "stateExpired": "State koneksi kedaluwarsa. Silakan coba lagi.",
+ "verifyError": "Tidak dapat memverifikasi dengan Openship Cloud.",
+ "finalizingTitle": "Menyelesaikan koneksi…",
+ "finalizingSubtitle": "Sebentar lagi.",
+ "successTitle": "Terhubung ke Openship Cloud",
+ "successDescription": "Instance Anda sekarang terhubung. Anda dapat menutup jendela ini.",
+ "failedTitle": "Koneksi Gagal"
+ },
+ "iconPicker": {
+ "chooseIcon": "Pilih Ikon",
+ "searchPlaceholder": "Cari ikon...",
+ "count": "{offset} dari {total}",
+ "startSearching": "Mulai Mencari",
+ "startSearchingHint": "Masukkan kata kunci untuk menemukan ikon",
+ "searching": "Mencari ikon...",
+ "noIconsFound": "Tidak Ada Ikon Ditemukan",
+ "noIconsHint": "Coba istilah pencarian lain",
+ "loadingMore": "Memuat lebih banyak...",
+ "noMoreResults": "Tidak ada hasil lagi",
+ "cancel": "Batal",
+ "selectIcon": "Pilih Ikon",
+ "chooseAnIcon": "Pilih sebuah ikon"
+ },
+ "restoreWizard": {
+ "startFailed": "Gagal memulai pemulihan",
+ "typeToConfirm": "Ketik nama layanan persis untuk mengonfirmasi",
+ "applyFailed": "Gagal menerapkan pemulihan",
+ "title": "Pulihkan backup",
+ "serviceLabel": "Layanan: {name}",
+ "serviceRestore": "Pemulihan layanan",
+ "stepReview": "Tinjau",
+ "stepPrepare": "Siapkan",
+ "stepConfirm": "Konfirmasi",
+ "stepApply": "Terapkan",
+ "stepDone": "Selesai",
+ "reviewPre": "Anda akan memulihkan dari backup yang diambil pada ",
+ "reviewPost": ".",
+ "sizeArtifacts": "Ukuran: {size} · Artefak: {count}",
+ "overwriteWarning": "Ini akan menimpa data saat ini.",
+ "overwriteHint": "Langkah berikutnya (Siapkan) akan memverifikasi backup dapat diunduh. Tidak ada yang merusak terjadi sampai Anda mengonfirmasi di langkah Terapkan.",
+ "protectLabel": "Lindungi backup terbaru dari penghapusan retensi",
+ "protectHint": "Menjaga backup berhasil terbaru tetap aman dari penghapusan otomatis, sehingga Anda dapat kembali ke \"tepat sebelum pemulihan ini\" jika terjadi kesalahan.",
+ "cancel": "Batal",
+ "starting": "Memulai…",
+ "continuePrepare": "Lanjutkan → Siapkan",
+ "verifying": "Memverifikasi backup…",
+ "verifyingHint": "Memeriksa keberadaan + integritas artefak di tujuan. Layanan masih berjalan normal.",
+ "cancelRestore": "Batalkan pemulihan",
+ "verified": "Backup terverifikasi, siap diterapkan.",
+ "preparedInfo": "{size} disiapkan. Layanan akan berhenti sebentar saat pemulihan berjalan.",
+ "confirmPre": "Data volume saat ini akan ",
+ "confirmStrong": "diganti secara permanen",
+ "confirmMid": ". Ketik ",
+ "confirmPost": " untuk mengonfirmasi.",
+ "applying": "Menerapkan…",
+ "applyRestore": "Terapkan pemulihan",
+ "restoringData": "Memulihkan data…",
+ "restoringHint": "Layanan berhenti → pertukaran data → memulai ulang. Jangan tutup jendela ini.",
+ "cancelFailed": "Gagal membatalkan pemulihan",
+ "cancelPending": "Pembatalan diminta. Pemulihan berhenti di titik aman berikutnya — jendela ini akan menampilkan hasilnya.",
+ "destructiveNotYet": "Belum ada yang ditulis, jadi membatalkan sekarang aman.",
+ "destructiveNow": "Data sedang ditulis. Membatalkan sekarang meninggalkan data parsial.",
+ "abortRestore": "Batalkan pemulihan",
+ "abortTitle": "Batalkan pemulihan ini?",
+ "abortBodyClean": "Pemulihan belum menulis apa pun. Membatalkan sekarang membiarkan data Anda apa adanya; layanan dimulai ulang jika kita menghentikannya.",
+ "abortBodyDestructive": "Pemulihan sudah mulai menulis. Membatalkan sekarang meninggalkan target dengan data parsial, dan layanan sengaja dibiarkan berhenti — jalankan ulang pemulihan ini (atau yang lebih baru) hingga selesai sebelum memulainya.",
+ "abortKeepGoing": "Lanjutkan memulihkan",
+ "abortConfirm": "Ya, batalkan",
+ "partialDataNotice": "Target pemulihan menampung data parsial dan layanan sengaja dibiarkan berhenti. Jalankan ulang pemulihan ini (atau yang lebih baru) hingga selesai sebelum memulainya.",
+ "partialDataRunningNotice": "Target pemulihan menampung data parsial dan layanan masih berjalan di atasnya. Hentikan layanan, lalu jalankan ulang pemulihan ini (atau yang lebih baru) hingga selesai.",
+ "serviceDownNotice": "Data Anda dibiarkan tidak berubah, tetapi layanan tidak berjalan. Mulai lagi saat Anda siap.",
+ "restoreComplete": "Pemulihan selesai.",
+ "restoreStatus": "Pemulihan {status}",
+ "failed": "gagal",
+ "close": "Tutup"
+ },
+ "terminalTabs": {
+ "shell": "Shell {n}",
+ "close": "Tutup {label}",
+ "serverLimit": "Batas server: {max} shell bersamaan.",
+ "newShell": "Shell baru",
+ "noTerminals": "Tidak ada terminal terbuka.",
+ "expand": "Perluas",
+ "collapse": "Ciutkan"
+ },
+ "buildPage": {
+ "thisRepo": "repo ini",
+ "notFoundTitle": "Deployment tidak ditemukan",
+ "notFoundDescription": "Deployment ini tidak ada atau Anda tidak memiliki akses ke sana. Mungkin telah dihapus atau tautannya salah.",
+ "viewDeployments": "Lihat Deployment",
+ "goHome": "Ke Beranda"
+ },
+ "backups": {
+ "usedBy": "Digunakan oleh",
+ "usedByDesc": "Proyek dan layanan yang melakukan backup ke tujuan ini",
+ "noUsageTitle": "Belum ada yang membackup ke sini",
+ "noUsageDesc": "Ketika proyek atau layanan membackup ke tujuan ini, ia muncul di sini.",
+ "scheduleManual": "Manual",
+ "projectDefault": "Default proyek",
+ "mailServer": "Server email",
+ "openInProject": "Buka di proyek",
+ "notFound": "Tujuan tidak ditemukan",
+ "summaryTitle": "Penyimpanan",
+ "summaryStored": "Total tersimpan",
+ "summaryBackups": "Backup",
+ "summaryDestinations": "Tujuan",
+ "statsStored": "tersimpan",
+ "statsBackups": "backup",
+ "statsNoRuns": "Belum ada backup",
+ "statsLast": "terakhir",
+ "title": "Backup",
+ "subtitle": "Target penyimpanan eksternal untuk backup proyek dan layanan",
+ "addDestination": "Tambah Tujuan",
+ "kindS3": "S3-compatible",
+ "kindSftp": "SFTP",
+ "kindServer": "Server yang ada",
+ "kindLocal": "Disk lokal",
+ "kindHttp": "Upload HTTP",
+ "verifiedSuccess": "\"{name}\" berhasil diverifikasi.",
+ "verificationFailedMsg": "Verifikasi gagal.",
+ "verificationFailedTitle": "Verifikasi gagal",
+ "setDefaultSuccess": "\"{name}\" sekarang menjadi tujuan default.",
+ "setDefaultFailed": "Tidak dapat mengatur default",
+ "deletedSuccess": "\"{name}\" dihapus.",
+ "deleteFailed": "Penghapusan gagal",
+ "updated": "Tujuan diperbarui.",
+ "created": "Tujuan dibuat.",
+ "verifyConnection": "Verifikasi koneksi",
+ "verifyingBadge": "Memverifikasi…",
+ "verifiedBadge": "Terverifikasi",
+ "failedBadge": "Gagal",
+ "notVerifiedBadge": "Belum diverifikasi",
+ "defaultBadge": "Default",
+ "defaultTitle": "Tujuan default",
+ "lastVerified": "Terakhir diverifikasi {date}",
+ "editAction": "Edit",
+ "setDefaultAction": "Atur sebagai default",
+ "deleteAction": "Hapus",
+ "emptyTitle": "Belum ada tujuan backup",
+ "emptyDescription": "Hubungkan bucket S3-compatible, host SFTP, server openship yang ada, atau disk lokal — layanan kemudian dapat melakukan backup ke sana.",
+ "addFirst": "Tambah Tujuan Pertama Anda",
+ "supportedTitle": "Tujuan yang didukung",
+ "cardS3Sub": "AWS · R2 · B2 · MinIO",
+ "cardSftpSub": "Host SSH mana pun",
+ "cardServerSub": "Gunakan ulang kredensial SSH",
+ "cardLocalSub": "Self-hosted saja",
+ "deleteTitle": "Hapus tujuan",
+ "deletePre": "Hapus ",
+ "deletePost": "? Ini tidak dapat dibatalkan. Tujuan yang masih digunakan oleh kebijakan backup tidak dapat dihapus.",
+ "cancel": "Batal",
+ "credAccessKeyStored": "Access key tersimpan",
+ "credPrivateKeyStored": "Private key tersimpan",
+ "credPasswordStored": "Password tersimpan",
+ "credNone": "Tanpa kredensial",
+ "credReusesServer": "Menggunakan ulang kredensial server",
+ "credNoneNeeded": "Tidak perlu kredensial",
+ "serverPrefix": "server ",
+ "s3Desc": "Penyimpanan S3-API mana pun — AWS S3, Cloudflare R2, Backblaze B2, Wasabi, MinIO.",
+ "s3Examples": "Paling populer · berfungsi di mana saja",
+ "sftpDesc": "Mendorong backup melalui SSH ke host mana pun dengan kredensial baru.",
+ "sftpExamples": "Kotak backup khusus · NAS",
+ "serverDesc": "Gunakan ulang kredensial SSH dari server yang telah Anda tambahkan ke Openship.",
+ "serverExamples": "Menggunakan ulang kredensial /servers",
+ "localDesc": "Menulis ke direktori di host API. Dinonaktifkan dalam mode cloud.",
+ "localExamples": "Self-hosted saja",
+ "modalEditTitle": "Edit {name}",
+ "modalAddTitle": "Tambah tujuan backup",
+ "modalNewTitle": "Baru {name}",
+ "destinationFallback": "tujuan",
+ "modalPickSubtitle": "Pilih di mana backup Anda akan disimpan.",
+ "modalConfigureSubtitle": "Kredensial dienkripsi saat disimpan dan tidak pernah ditampilkan kembali.",
+ "encryptedAtRest": "Dienkripsi saat disimpan",
+ "backToPicker": "Kembali ke pemilih jenis",
+ "secretStoredPlaceholder": "•••••• tersimpan — biarkan kosong untuk mempertahankan",
+ "updateFailed": "Gagal memperbarui tujuan",
+ "createFailed": "Gagal membuat tujuan",
+ "fieldName": "Nama",
+ "providerLabel": "Penyedia",
+ "providerAws": "AWS S3",
+ "providerR2": "Cloudflare R2",
+ "providerB2": "Backblaze B2",
+ "providerWasabi": "Wasabi",
+ "providerDo": "DigitalOcean Spaces",
+ "providerMinio": "MinIO / Kustom",
+ "fieldAccountId": "ID Akun",
+ "hintAccountId": "ID akun Cloudflare Anda — digunakan untuk membuat endpoint R2.",
+ "hintEndpointMinio": "URL endpoint S3 lengkap, misalnya https://minio.example.com:9000",
+ "testConnection": "Uji koneksi",
+ "testing": "Menguji koneksi…",
+ "testOk": "Terhubung — bucket dapat diakses dan ditulis.",
+ "fieldEndpoint": "Endpoint",
+ "hintEndpoint": "Kosong = AWS S3. R2: https://.r2.cloudflarestorage.com",
+ "fieldRegion": "Region",
+ "fieldBucket": "Bucket",
+ "fieldPathPrefix": "Prefix path",
+ "hintPathPrefix": "Subpath opsional di dalam bucket",
+ "fieldAccessKeyId": "Access key ID",
+ "fieldSecretAccessKey": "Secret access key",
+ "fieldHost": "Host",
+ "fieldPort": "Port",
+ "fieldUser": "Pengguna",
+ "fieldPassword": "Password",
+ "hintPassword": "Biarkan kosong jika menggunakan private key",
+ "fieldPrivateKey": "Private key (PEM)",
+ "hintPrivateKey": "Digunakan saat password kosong",
+ "fieldServer": "Server",
+ "hintServer": "Menggunakan ulang kredensial SSH yang sudah disimpan di /servers",
+ "selectServer": "— pilih server —",
+ "noServersPre": "Belum ada server yang dikonfigurasi. Tambah satu di ",
+ "noServersLink": "Server",
+ "noServersPost": " terlebih dahulu.",
+ "fieldRemotePath": "Path remote",
+ "hintRemotePath": "Direktori absolut di server (misalnya /backups/openship)",
+ "fieldAbsolutePath": "Path absolut",
+ "hintAbsolutePath": "Path filesystem di host API. Dinonaktifkan dalam mode cloud.",
+ "saving": "Menyimpan…",
+ "saveChanges": "Simpan perubahan",
+ "saveDestination": "Simpan tujuan"
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/id/onboarding.json b/apps/dashboard/src/i18n/locales/id/onboarding.json
new file mode 100644
index 000000000..51b616b22
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/id/onboarding.json
@@ -0,0 +1,149 @@
+{
+ "common": {
+ "goBack": "Kembali",
+ "continue": "Lanjutkan",
+ "back": "Kembali",
+ "optional": "(opsional)"
+ },
+ "topBar": {
+ "website": "Situs web",
+ "github": "GitHub",
+ "theme": "Ubah tema",
+ "language": "Bahasa"
+ },
+ "choose": {
+ "title": "Selamat datang di OpenShip",
+ "subtitle": "Pilih cara menjalankan OpenShip. Kedua opsi memberikan setiap fitur — dan Anda bisa beralih kapan saja.",
+ "cloud": {
+ "name": "OpenShip Cloud",
+ "desc": "Kami menangani infrastruktur, pembaruan, dan backup. Cukup masuk dan mulai deploy.",
+ "perk1": "Tanpa server",
+ "perk2": "Tier gratis disertakan",
+ "perk3": "Terkelola & selalu terbaru",
+ "cta": "Lanjutkan dengan Cloud"
+ },
+ "or": "atau",
+ "selfhost": {
+ "name": "Self-Hosted",
+ "desc": "Jalankan OpenShip di perangkat keras Anda sendiri. Hubungkan server remote atau gunakan mesin ini.",
+ "perk1": "Kepemilikan data penuh",
+ "perk2": "Server remote atau Mac ini",
+ "perk3": "Kami menangani sisanya",
+ "cta": "Atur Server Saya"
+ },
+ "migrateNote": "Beralih antara Cloud dan Self-Hosted kapan saja dengan satu klik — tanpa penguncian, selamanya.",
+ "skip": "Lewati untuk saat ini"
+ },
+ "selfhost": {
+ "title": "Di mana OpenShip harus berjalan?",
+ "subtitleLine1": "Pilih di mana menginstal platform OpenShip.",
+ "subtitleLine2": "Kedua opsi memberikan kendali penuh dan kepemilikan data.",
+ "local": {
+ "title": "Mesin Ini",
+ "badge": "Beta",
+ "desc": "Instal OpenShip di sini — tanpa SSH, tanpa pengaturan remote",
+ "comingSoon": "Segera hadir",
+ "comingSoonDesc": "Menjalankan OpenShip langsung di mesin ini belum siap — hubungkan server untuk saat ini."
+ },
+ "remote": {
+ "title": "Server Lain",
+ "desc": "Hubungkan ke server remote, VPS, atau mesin mana pun di jaringan Anda melalui SSH"
+ }
+ },
+ "ssh": {
+ "title": "Hubungkan ke server Anda",
+ "subtitleLine1": "Masukkan detail server Anda dan pilih metode autentikasi.",
+ "subtitleLine2": "Kami akan menginstal dan mengonfigurasi semuanya secara otomatis.",
+ "serverNameLabel": "Nama Server",
+ "serverNamePlaceholder": "Produksi, Staging, Dev…",
+ "serverIpLabel": "Alamat IP Server",
+ "usernameLabel": "Nama pengguna",
+ "methodPassword": "Password",
+ "methodKey": "SSH Key",
+ "methodAgent": "Agent",
+ "agentHint": "Menggunakan SSH agent mesin ini — seperti VSCode, tanpa password atau key. Host yang menjalankan OpenShip harus sudah memiliki akses ke server ini (key yang dimuat di ssh-agent-nya).",
+ "passwordLabel": "Password",
+ "passwordPlaceholder": "Password server",
+ "keyPathLabel": "Path Key",
+ "browse": "Jelajahi",
+ "passphraseLabel": "Passphrase",
+ "passphrasePlaceholder": "Kosongkan jika tidak ada",
+ "advanced": "Lanjutan",
+ "sshPortLabel": "Port SSH",
+ "jumpHostLabel": "Jump Host",
+ "extraArgsLabel": "Argumen SSH Tambahan",
+ "submit": "Simpan & Lanjutkan",
+ "tutorial": "Tutorial pengaturan langkah demi langkah",
+ "testConnection": "Uji koneksi",
+ "testing": "Menguji…",
+ "connected": "Tersambung",
+ "testFailed": "Koneksi gagal",
+ "testHint": "Pastikan server dapat diakses sebelum melanjutkan."
+ },
+ "tunnel": {
+ "title": "Akses Internet",
+ "subtitle": "Server Anda perlu cara untuk dapat diakses dari internet untuk webhook, SSL, dan akses publik.",
+ "providers": {
+ "edge": {
+ "title": "OpenShip Edge",
+ "desc": "Zero config. SSL otomatis, ketersediaan tinggi, dan perlindungan DDoS — semuanya sudah termasuk.",
+ "badge": "Direkomendasikan"
+ },
+ "cloudflare": {
+ "title": "Cloudflare Tunnel",
+ "desc": "Gunakan akun Cloudflare Anda sendiri. Tier gratis tersedia."
+ },
+ "ngrok": {
+ "title": "ngrok",
+ "desc": "Exposes layanan dengan auth token ngrok Anda."
+ }
+ },
+ "cloudflareTokenLabel": "Token Cloudflare Tunnel",
+ "ngrokTokenLabel": "Auth Token ngrok",
+ "cloudflarePlaceholder": "Tempel token tunnel Anda",
+ "ngrokPlaceholder": "Tempel auth token ngrok Anda",
+ "edgeHint": "OpenShip Edge memerlukan akun OpenShip. Anda akan masuk untuk melanjutkan.",
+ "signInContinue": "Masuk & Lanjutkan"
+ },
+ "preferences": {
+ "title": "Preferensi Build",
+ "subtitleLine1": "Pilih cara proyek Anda dibangun saat deploy.",
+ "subtitleLine2": "Anda bisa mengubah ini nanti di pengaturan.",
+ "options": {
+ "auto": {
+ "title": "Otomatis",
+ "desc": "Kami memilih opsi terbaik per framework"
+ },
+ "server": {
+ "title": "Remote",
+ "desc": "Bangun di server deployment langsung"
+ },
+ "local": {
+ "title": "Lokal",
+ "desc": "Bangun di Mac ini, lalu push ke server"
+ }
+ },
+ "hint": "Git push deployment selalu membangun di server — ini hanya berlaku untuk deploy manual."
+ },
+ "loading": {
+ "connecting": "Menghubungkan…",
+ "verifying": "Memverifikasi server Anda",
+ "retry": "Coba lagi",
+ "openingCloud": "Membuka OpenShip Cloud…",
+ "waitingAuth": "Menunggu autentikasi selesai",
+ "couldNotStartAuthTitle": "Tidak dapat memulai autentikasi cloud",
+ "authFailedTitle": "Autentikasi cloud gagal",
+ "couldNotStartAuthMsg": "Alur autentikasi desktop tidak dapat dimulai.",
+ "authFailedMsg": "Silakan coba lagi dan selesaikan masuk di browser Anda.",
+ "completingSignIn": "Menyelesaikan masuk…",
+ "returningToOpenship": "Kembali ke OpenShip",
+ "redirectingCloud": "Mengalihkan ke OpenShip Cloud…",
+ "newTabSignIn": "Anda akan menyelesaikan masuk di tab baru",
+ "couldNotSaveTitle": "Tidak dapat menyimpan konfigurasi",
+ "couldNotSaveMsg": "API tidak merespons. Pastikan layanan berjalan.",
+ "savingConfig": "Menyimpan konfigurasi…",
+ "almostThere": "Hampir sampai",
+ "settingUpAccount": "Mengatur akun Anda…",
+ "creatingSession": "Membuat sesi Anda"
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/id/overview.json b/apps/dashboard/src/i18n/locales/id/overview.json
new file mode 100644
index 000000000..0c74b62c2
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/id/overview.json
@@ -0,0 +1,144 @@
+{
+ "activity": {
+ "title": "Aktivitas",
+ "thisWeek": "minggu ini",
+ "deploymentSingular": "{count} deployment",
+ "deploymentPlural": "{count} deployment"
+ },
+ "apiRequests": {
+ "title": "Request API",
+ "last7Days": "7 hari terakhir",
+ "totalRequests": "Total request",
+ "requestsTooltip": "{count} request"
+ },
+ "credits": {
+ "title": "Saldo Kredit",
+ "availableToUse": "Tersedia untuk digunakan",
+ "spentThisMonth": "Digunakan bulan ini",
+ "add": "Tambah"
+ },
+ "deployments": {
+ "title": "Deployment",
+ "lastNDays": "{period} hari terakhir",
+ "deploymentSingular": "{count} deployment",
+ "deploymentPlural": "{count} deployment"
+ },
+ "homeTip": {
+ "quickTip": "Tips Cepat",
+ "connectText": "Hubungkan repository GitHub Anda untuk deployment otomatis setiap push.",
+ "connectLabel": "Hubungkan GitHub",
+ "createText": "Buat proyek pertama Anda untuk mulai deploy.",
+ "createLabel": "Proyek Baru",
+ "settingsText": "Atur environment variable dan domain kustom di pengaturan proyek.",
+ "settingsLabel": "Pengaturan Proyek",
+ "connected": "Tersambung",
+ "tips": {
+ "envVars": {
+ "text": "Tambahkan environment variable terenkripsi dan secret ke proyek mana pun.",
+ "label": "Buka proyek"
+ },
+ "customDomain": {
+ "text": "Arahkan domain kustom ke proyek — HTTPS disediakan secara otomatis.",
+ "label": "Buka proyek"
+ },
+ "autoDeploy": {
+ "text": "Hubungkan GitHub untuk auto-deploy setiap push, tanpa CI.",
+ "label": "Buka pengaturan Git"
+ },
+ "rollback": {
+ "text": "Kembalikan ke deployment sebelumnya dengan satu klik.",
+ "label": "Lihat deployment"
+ },
+ "apps": {
+ "text": "Instal aplikasi self-hosted seperti Gitea, n8n, dan Metabase dengan satu klik.",
+ "label": "Jelajahi aplikasi"
+ },
+ "servers": {
+ "text": "Hubungkan server Anda sendiri melalui SSH dan deploy dari sini.",
+ "label": "Kelola server"
+ },
+ "jobs": {
+ "text": "Jadwalkan command dengan cron job — retry, log langsung, dan riwayat termasuk.",
+ "label": "Buka job"
+ },
+ "backups": {
+ "text": "Atur backup otomatis untuk database dan volume Anda.",
+ "label": "Konfigurasi backup"
+ },
+ "mail": {
+ "text": "Jalankan server email dan webmail Anda sendiri, sepenuhnya self-hosted.",
+ "label": "Atur email"
+ },
+ "team": {
+ "text": "Undang rekan tim dan batasi akses mereka per proyek.",
+ "label": "Kelola tim"
+ }
+ }
+ },
+ "welcome": {
+ "title": "Luncurkan proyek pertama Anda",
+ "subtitle": "Hubungkan repository atau mulai dari template — OpenShip membangunnya, mengirimkannya, dan memberikan URL aktif dalam hitungan menit.",
+ "createProject": "Buat proyek",
+ "importGithub": "Impor dari GitHub",
+ "tipPrefix": "Tips: tekan",
+ "tipSuffix": "untuk melompat ke mana saja"
+ },
+ "header": {
+ "goodMorning": "Selamat pagi",
+ "goodAfternoon": "Selamat siang",
+ "goodEvening": "Selamat malam",
+ "greetingWithName": "{greeting}, {name}",
+ "overview": "Ringkasan",
+ "subtitle": "Pantau penggunaan platform dan kelola sumber daya",
+ "creditsBalance": "Saldo Kredit",
+ "topUp": "Isi Ulang"
+ },
+ "hero": {
+ "title": "Aktivitas Platform",
+ "subtitle": "Penggunaan token & request API",
+ "tokens": "Token",
+ "requests": "Request",
+ "lastNDays": "{period} hari terakhir",
+ "agents": "Agent",
+ "environments": "Environment",
+ "total": "Total",
+ "production": "Produksi",
+ "testing": "Pengujian",
+ "prodCount": "{count} produksi",
+ "testCount": "{count} uji"
+ },
+ "projects": {
+ "title": "Proyek",
+ "activeDeployments": "Deployment aktif",
+ "total": "Total",
+ "live": "Aktif",
+ "building": "Membangun",
+ "needAttentionSingular": "{count} proyek perlu perhatian",
+ "needAttentionPlural": "{count} proyek perlu perhatian"
+ },
+ "sandboxes": {
+ "title": "Sandbox",
+ "developmentEnvironments": "Environment pengembangan",
+ "totalSandboxes": "Total sandbox",
+ "active": "Aktif",
+ "runningCount": "{count} berjalan",
+ "stoppedCount": "{count} berhenti"
+ },
+ "stats": {
+ "creditsBalance": "Saldo Kredit",
+ "tokenUsage": "Penggunaan Token",
+ "thisMonth": "Bulan ini",
+ "apiRequests": "Request API",
+ "last7Days": "7 hari terakhir",
+ "deployments": "Deployment",
+ "successfulCount": "{count} berhasil",
+ "activeProjects": "Proyek Aktif",
+ "sandboxes": "Sandbox",
+ "activeCount": "{count} aktif"
+ },
+ "tokenUsage": {
+ "title": "Penggunaan Token",
+ "lastNDays": "{period} hari terakhir",
+ "tokensTooltip": "{count} token"
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/id/projectDetail.json b/apps/dashboard/src/i18n/locales/id/projectDetail.json
new file mode 100644
index 000000000..daf57eb01
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/id/projectDetail.json
@@ -0,0 +1,276 @@
+{
+ "general": {
+ "productionUrl": {
+ "title": "URL Produksi",
+ "notConfigured": "Belum dikonfigurasi"
+ },
+ "identity": {
+ "title": "Identitas Proyek",
+ "nameLabel": "Nama Proyek",
+ "namePlaceholder": "proyek-keren-saya",
+ "unnamed": "Proyek Tanpa Nama",
+ "descriptionLabel": "Deskripsi",
+ "descriptionPlaceholder": "Aplikasi web modern yang dibangun dengan…",
+ "noDescription": "Tidak ada deskripsi",
+ "save": "Simpan",
+ "cancel": "Batal",
+ "updateNameFailed": "Gagal memperbarui nama proyek",
+ "updateDescriptionFailed": "Gagal memperbarui deskripsi proyek"
+ },
+ "info": {
+ "title": "Info Proyek",
+ "projectId": "ID Proyek",
+ "branch": "Branch",
+ "lastDeploy": "Deploy Terakhir",
+ "framework": "Framework",
+ "unknown": "Tidak diketahui",
+ "aiTitle": "Asisten AI",
+ "aiPoweredBy": "Didukung oleh Blurs",
+ "aiEdit": "Edit dengan Blurs",
+ "aiComingSoonTitle": "Segera hadir",
+ "aiComingSoonBody": "Mengedit dengan Blurs belum tersedia"
+ },
+ "topPaths": {
+ "title": "Path Teratas",
+ "showLess": "Tampilkan Lebih Sedikit",
+ "requestsShort": "request"
+ },
+ "traffic": {
+ "title": "Ringkasan Lalu Lintas",
+ "last24Hours": "24 jam terakhir",
+ "bar": "Batang",
+ "area": "Area",
+ "loading": "Memuat analitik…",
+ "noDataTitle": "Belum ada data lalu lintas",
+ "noDataBody": "Grafik ini akan terisi setelah aplikasi menerima request."
+ }
+ },
+ "logs": {
+ "actions": {
+ "copied": "Disalin",
+ "copy": "Salin",
+ "download": "Unduh",
+ "clear": "Hapus"
+ },
+ "server": {
+ "title": "Log Request HTTP",
+ "live": "Langsung",
+ "errorHint": "Pastikan server memiliki skrip analitik yang di-deploy",
+ "waiting": "Menunggu request masuk…",
+ "streamError": "Kesalahan streaming",
+ "connectionLost": "Koneksi ke log stream terputus",
+ "connectFailed": "Gagal menghubungkan ke log stream",
+ "noDomain": "Tambah domain untuk melihat log request",
+ "noDomainHint": "Log request HTTP dikumpulkan per domain — tambahkan domain ke proyek ini terlebih dahulu."
+ },
+ "terminal": {
+ "fallbackName": "Terminal",
+ "searchPlaceholder": "Cari di log…",
+ "previousMatch": "Cocokan sebelumnya (Shift+Enter)",
+ "nextMatch": "Cocokan berikutnya (Enter)",
+ "lines": "{count} baris",
+ "stop": "Hentikan",
+ "start": "Mulai",
+ "waitingForLogs": "Menunggu log…",
+ "pressStart": "Tekan Mulai untuk mulai streaming log",
+ "copiedToClipboard": "Disalin ke clipboard",
+ "logsErrorTitle": "Kesalahan Log",
+ "containerStoppedTitle": "Container Berhenti",
+ "containerExited": "Container keluar dengan kode {code}",
+ "connectFailedTitle": "Gagal menghubungkan ke log terminal",
+ "connectFailed": "Gagal menghubungkan ke log terminal, pastikan proyek Anda berjalan"
+ }
+ },
+ "services": {
+ "addModal": {
+ "addTitle": "Tambah aplikasi atau layanan",
+ "configureTitle": "Konfigurasi {name}",
+ "fallbackService": "aplikasi",
+ "pickSubtitle": "Pilih dari katalog atau gunakan gambar kustom.",
+ "configureSubtitle": "Atur nama, environment, dan eksposur.",
+ "backToCatalog": "Kembali ke katalog",
+ "close": "Tutup",
+ "browse": "Jelajahi",
+ "allServices": "Semua layanan",
+ "customImage": "Gambar kustom",
+ "services": "Layanan",
+ "serviceCountOne": "{count} layanan",
+ "serviceCountOther": "{count} layanan",
+ "searchPlaceholder": "Cari berdasarkan nama, gambar, atau tag…",
+ "cloudConnectHint": "Hubungkan OpenShip Cloud untuk menjelajahi katalog layanan cloud. Anda tetap bisa menambah gambar apa pun secara manual.",
+ "cloudConnectTitle": "Hubungkan OpenShip Cloud",
+ "cloudConnectBody": "Tautkan instance ini ke OpenShip Cloud untuk menjelajahi dan menambah gambar cloud terkelola. Anda tetap bisa menambah gambar apa pun secara manual dari Gambar Lokal.",
+ "cloudConnectButton": "Hubungkan ke OpenShip Cloud",
+ "noMatchesTitle": "Tidak ada yang cocok",
+ "noMatchesBody": "Coba kategori atau istilah pencarian yang berbeda — atau pilih Gambar kustom dari panel.",
+ "localImages": "Gambar lokal",
+ "openshipCloud": "OpenShip Cloud",
+ "localDocker": "Docker Lokal",
+ "customEntryName": "Gambar kustom",
+ "customEntryDescription": "Tarik gambar Docker publik atau privat apa pun berdasarkan tag.",
+ "unnamed": "Tanpa nama",
+ "serviceName": "Nama",
+ "ports": "Port",
+ "image": "Gambar",
+ "persistentVolumes": "Volume persisten",
+ "preSeeded": "Pre-seeded untuk layanan ini",
+ "optional": "Opsional",
+ "removeVolume": "Hapus volume",
+ "addVolume": "Tambah volume",
+ "volumesHint": "Volume menjaga data melintasi restart. Nama di kiri, path container di kanan.",
+ "environmentVariables": "Environment variable",
+ "cancel": "Batal",
+ "addService": "Tambah",
+ "serviceNameRequired": "Nama diperlukan.",
+ "imageRequired": "Gambar diperlukan.",
+ "addServiceFailed": "Gagal menambah layanan",
+ "categories": {
+ "database": "Database",
+ "cache": "Cache",
+ "search": "Pencarian",
+ "vector": "Vektor & AI",
+ "queue": "Antrian & Stream",
+ "storage": "Penyimpanan Objek",
+ "auth": "Aut & Identitas",
+ "mail": "Email & SMTP",
+ "runtime": "Runtime",
+ "other": "Lainnya"
+ }
+ },
+ "detail": {
+ "tabs": {
+ "overview": "Ringkasan",
+ "terminal": "Terminal",
+ "logs": "Log",
+ "env": "Environment",
+ "settings": "Pengaturan",
+ "backup": "Backup"
+ },
+ "status": {
+ "running": "Berjalan",
+ "stopped": "Berhenti",
+ "disabled": "Nonaktif",
+ "failed": "Gagal",
+ "starting": "Memulai",
+ "restarting": "Memulai ulang",
+ "unknown": "Tidak diketahui"
+ },
+ "network": "Jaringan",
+ "configuration": "Konfigurasi",
+ "volumes": "Volume",
+ "terminal": "Terminal",
+ "backup": "Backup",
+ "ports": "Port",
+ "hostPort": "Port Host",
+ "containerIp": "IP Container",
+ "internalAddress": "Alamat internal",
+ "internalAddressHint": "Layanan lain menjangkau layanan ini di alamat ini.",
+ "currentIp": "IP Saat Ini",
+ "currentIpHint": "Berubah saat restart — gunakan alamat internal di atas.",
+ "workspaceId": "ID Workspace",
+ "containerId": "ID Container",
+ "restartPolicy": "Kebijakan Restart",
+ "command": "Command",
+ "dependsOn": "Bergantung pada",
+ "startShellHint": "Mulai layanan untuk membuka shell.",
+ "saveEnvironment": "Simpan environment",
+ "stop": "Hentikan",
+ "restart": "Mulai ulang",
+ "start": "Mulai",
+ "starting": "Memulai…",
+ "enableAndStart": "Aktifkan & mulai",
+ "redeploying": "Deploy ulang…",
+ "redeploy": "Deploy ulang",
+ "disableService": "Nonaktifkan Layanan",
+ "enableService": "Aktifkan Layanan",
+ "delete": "Hapus",
+ "backupSubtitle": {
+ "manualOnly": "hanya manual",
+ "cron": "cron {expr}",
+ "preDeploy": "pra-deploy",
+ "webhook": "webhook",
+ "none": "Tidak ada kebijakan backup untuk layanan ini"
+ },
+ "backupNow": "Backup sekarang",
+ "editPolicy": "Edit kebijakan",
+ "createPolicy": "Buat kebijakan",
+ "deleteTitle": "Hapus layanan",
+ "deleteBody": "Ini menghapus \"{name}\" dari proyek dan membersihkan container serta route aktifnya.",
+ "deleteCancel": "Batal",
+ "deleteConfirm": "Hapus layanan",
+ "toast": {
+ "envUpdated": "Environment diperbarui",
+ "envSaveFailed": "Gagal menyimpan environment",
+ "deployFirstStart": "Deploy proyek terlebih dahulu, lalu layanan ini akan dimulai",
+ "deployFailed": "Deploy gagal",
+ "serviceStarting": "{name} sedang memulai",
+ "serviceTitle": "Layanan",
+ "deployFirstRedeploy": "Deploy proyek terlebih dahulu, lalu Anda bisa deploy ulang layanan ini",
+ "enableBeforeRedeploy": "Aktifkan layanan sebelum deploy ulang",
+ "redeployFailed": "Deploy ulang gagal",
+ "stillStarting": "Masih memulai — periksa log untuk perkembangan",
+ "serviceUpdated": "Layanan diperbarui",
+ "updateFailed": "Gagal memperbarui layanan",
+ "serviceDeleted": "Layanan dihapus",
+ "deleteFailed": "Gagal menghapus layanan",
+ "backupRunFailed": "Jalankan backup gagal"
+ },
+ "duplicateContainers": "Container lain juga cocok dengan layanan ini: {names}",
+ "editInSettings": "Edit di pengaturan"
+ },
+ "settingsForm": {
+ "name": "Nama",
+ "rootDirectory": "Direktori root",
+ "framework": "Framework",
+ "packageManager": "Manajer paket",
+ "buildImage": "Gambar build",
+ "installCommand": "Command instal",
+ "buildCommand": "Command build",
+ "startCommand": "Command mulai",
+ "outputDirectory": "Direktori output",
+ "image": "Gambar",
+ "dockerfile": "Dockerfile",
+ "buildContext": "Konteks build",
+ "ports": "Port",
+ "dependsOn": "Bergantung pada",
+ "command": "Command",
+ "restartPolicy": "Kebijakan restart",
+ "volumes": "Volume",
+ "healthcheck": "Healthcheck",
+ "healthcheckHint": "Command tes (shell form). Kosongkan untuk menggunakan cek default gambar.",
+ "enabled": "Aktif",
+ "enabledHint": "Layanan aktif di-deploy bersama proyek.",
+ "saveChanges": "Simpan perubahan",
+ "errors": {
+ "nameRequired": "Nama layanan diperlukan.",
+ "imageOrDockerfile": "Tambah nama gambar, atau konteks build / Dockerfile.",
+ "buildContextOrDockerfile": "Tambah konteks build atau path Dockerfile.",
+ "rootDirectory": "Tambah direktori root (misalnya apps/web).",
+ "buildOrStart": "Tambah setidaknya command build atau command mulai.",
+ "saveFailed": "Gagal menyimpan layanan."
+ },
+ "alias": "Alias jaringan",
+ "aliasHint": "Nama DNS tambahan yang bisa digunakan layanan lain untuk menjangkau ini, bersama nama layanannya. Kosongkan untuk default.",
+ "portsHint": "Port container, atau host:container. Tekan Enter untuk menambah masing-masing.",
+ "volumesHint": "Volume bernama atau path host, dipetakan ke path container. Tekan Enter untuk menambah masing-masing.",
+ "dependsOnHint": "Layanan yang harus dimulai sebelum ini.",
+ "dependsOnEmpty": "Belum ada layanan lain di proyek ini.",
+ "removeTag": "Hapus",
+ "sections": {
+ "general": "Umum",
+ "generalHint": "Nama dan apakah layanan ini di-deploy bersama proyek.",
+ "source": "Sumber",
+ "sourceHint": "Tarik gambar yang sudah dibangun, atau bangun dari Dockerfile di repo Anda.",
+ "sourceMonorepoHint": "Cara aplikasi ini diinstal, dibangun, dan dimulai.",
+ "networking": "Jaringan",
+ "networkingHint": "Port, alias DNS internal, dan urutan startup.",
+ "runtime": "Runtime",
+ "runtimeHint": "Command peluncuran, kebijakan restart, dan volume yang di-mount.",
+ "health": "Kesehatan & kesiapan",
+ "healthHint": "Cek liveness dan gerbang kesiapan saat deploy."
+ },
+ "orBuildFromSource": "atau bangun dari sumber",
+ "imageHint": "Kosongkan untuk membangun dari Dockerfile di bawah."
+ }
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/id/projectSettings.json b/apps/dashboard/src/i18n/locales/id/projectSettings.json
new file mode 100644
index 000000000..638032a65
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/id/projectSettings.json
@@ -0,0 +1,1064 @@
+{
+ "settingSection": {
+ "edit": "Edit",
+ "save": "Simpan",
+ "cancel": "Batal"
+ },
+ "appSource": {
+ "title": "Sumber",
+ "description": "Aplikasi ini di-deploy dari rilis — tidak ada repo untuk push; pembaruan dikirim sebagai versi baru.",
+ "sourceType": "Sumber",
+ "kindRelease": "Rilis",
+ "kindImage": "Gambar container",
+ "current": "Versi saat ini",
+ "latest": "Versi terbaru",
+ "repository": "Repository",
+ "notChecked": "Belum diperiksa",
+ "upToDate": "Sudah terbaru",
+ "updateAvailable": "Pembaruan tersedia",
+ "checkForUpdates": "Periksa pembaruan",
+ "checkFailed": "Tidak dapat memeriksa pembaruan",
+ "update": "Perbarui",
+ "updateTo": "Perbarui ke {version}",
+ "updating": "Memperbarui…",
+ "updateFailed": "Pembaruan gagal",
+ "toastTitle": "Sumber",
+ "cliManaged": "Ini adalah control plane OpenShip — ia memperbarui dirinya sendiri. Jalankan \"openship update\" di host untuk meningkatkannya; tidak ada yang perlu diubah di sini."
+ },
+ "releaseImageSource": {
+ "validationInvalid": "Sumber gambar rilis tidak valid.",
+ "toast": {
+ "saved": "Sumber rilis container disimpan.",
+ "saveFailed": "Tidak dapat menyimpan sumber rilis container."
+ },
+ "status": {
+ "notDeployed": "Belum di-deploy",
+ "checking": "Memeriksa…",
+ "unavailable": "Tidak tersedia"
+ },
+ "switchToImage": {
+ "title": "Beralih ke sumber gambar prebuilt",
+ "description": "Menyimpan menggantikan sumber Git atau lokal saat ini untuk setiap environment. Deployment dan riwayat rollback yang ada tetap tersedia.",
+ "keep": "Pertahankan sumber saat ini"
+ },
+ "switchToGit": {
+ "title": "Beralih ke sumber Git",
+ "description": "Tidak ada yang berubah sampai Anda menautkan repository. Menautkan menggantikan sumber gambar yang dilacak untuk setiap environment.",
+ "back": "Kembali ke sumber gambar"
+ },
+ "entry": {
+ "shortButton": "Gunakan gambar prebuilt",
+ "button": "Lacak gambar container prebuilt",
+ "description": "Deploy gambar registry yang di-version langsung, tanpa meng-clone atau membangun repository."
+ },
+ "summary": {
+ "title": "Gambar container yang dilacak",
+ "badge": "Prebuilt",
+ "description": "Deploy gambar yang dipublikasikan langsung. Tidak ada repository yang di-clone dan tidak ada command build yang dijalankan.",
+ "linkGit": "Tautkan repository Git",
+ "image": "Gambar",
+ "versionSource": "Sumber versi",
+ "githubReleases": "GitHub Releases",
+ "pinnedVersion": "Versi yang disematkan",
+ "currentVersion": "Versi saat ini",
+ "targetVersion": "Versi target",
+ "latestVersion": "Versi terbaru",
+ "updateAvailable": "Pembaruan tersedia"
+ },
+ "editor": {
+ "trackingTitle": "Pelacakan rilis",
+ "configureTitle": "Konfigurasi gambar prebuilt",
+ "templateHint": "Gunakan {tag} untuk mempertahankan tag pasti penerbit, atau {version} untuk versi yang dinormalisasi.",
+ "provider": "Penyedia versi",
+ "githubTitle": "GitHub Releases",
+ "githubDescription": "Ikuti tag rilis terbaru yang dipublikasikan.",
+ "urlTitle": "URL versi atau pin",
+ "urlDescription": "Baca endpoint versi atau deploy versi tetap.",
+ "imageTemplate": "Template gambar",
+ "imagePlaceholder": "ghcr.io/acme/api:{tag}",
+ "githubRepository": "Repository GitHub",
+ "repoPlaceholder": "owner/repository",
+ "versionUrl": "URL versi",
+ "versionUrlPlaceholder": "https://example.com/latest-version",
+ "versionUrlHint": "Endpoint HTTPS yang mengembalikan teks atau JSON dengan version/tag_name.",
+ "pinnedVersion": "Versi yang disematkan",
+ "pinnedPlaceholder": "Opsional, misalnya v2.4.1",
+ "pinnedHint": "Mengganti versi terbaru yang ditemukan; kosongkan untuk melacak pembaruan.",
+ "appliesAll": "Perubahan berlaku untuk setiap environment di proyek ini.",
+ "saving": "Menyimpan…",
+ "save": "Simpan sumber"
+ }
+ },
+ "appSettings": {
+ "modeApp": "Pengaturan aplikasi",
+ "modeDeployment": "Deployment",
+ "saveChanges": "Simpan perubahan",
+ "saving": "Menyimpan…",
+ "saved": "Pengaturan disimpan",
+ "saveHint": "Simpan untuk menyimpan perubahan ini — berlaku saat aplikasi di-deploy berikutnya.",
+ "applyNow": "Terapkan sekarang",
+ "applying": "Menerapkan…",
+ "applied": "Menerapkan perubahan",
+ "restartNote": "Terapkan sekarang untuk memulai ulang aplikasi dengan pengaturan baru, atau berlaku saat deploy berikutnya.",
+ "redeployNote": "Menerapkan perubahan ini membuat ulang aplikasi — Anda mungkin perlu mengautentikasi ulang dashboard atau CLI-nya setelahnya.",
+ "advancedShow": "Tampilkan lanjutan",
+ "advancedHide": "Sembunyikan lanjutan",
+ "secretSet": "Ditetapkan — kosongkan untuk mempertahankan nilai saat ini",
+ "loadError": "Tidak dapat memuat pengaturan aplikasi.",
+ "saveFailed": "Tidak dapat menyimpan pengaturan.",
+ "applyFailed": "Tidak dapat menerapkan perubahan.",
+ "unmanaged": "Aplikasi ini belum memiliki pengaturan yang dapat dikonfigurasi."
+ },
+ "appInstall": {
+ "install": "Instal",
+ "installing": "Menginstal…",
+ "advanced": "Opsi deploy lanjutan",
+ "nameLabel": "Nama",
+ "nameHint": "Cara aplikasi ini muncul di OpenShip.",
+ "domainTitle": "Di mana harus berada?",
+ "domainFree": "Subdomain gratis",
+ "domainFreeHint": "Subdomain gratis di {domain}",
+ "domainCustom": "Domain Anda sendiri",
+ "domainCustomPlaceholder": "app.example.com",
+ "domainNone": "Tanpa URL publik",
+ "domainNoneHint": "Hanya internal — tanpa alamat publik.",
+ "customRequired": "Masukkan domain Anda, atau pilih opsi lain.",
+ "progressPreparing": "Mempersiapkan…",
+ "progressDeploying": "Mendeploy…",
+ "progressLive": "Aplikasi Anda aktif",
+ "openApp": "Buka aplikasi",
+ "goToApp": "Ke aplikasi",
+ "installFailed": "Instalasi gagal",
+ "installFailedBody": "Berhenti sebelum semuanya berjalan. Detail teknis berisi log build lengkap.",
+ "installCancelledSelf": "Anda menghentikan instalasi ini.",
+ "installCancelledBody": "Container dan gambar yang dibuat dihapus, dan volume data dipertahankan — menginstal ulang menggunakannya kembali. Apa pun yang sudah aktif dibiarkan.",
+ "settledAtPhase": "Berhenti di {phase}",
+ "viewDetails": "Lihat detail teknis",
+ "showLogs": "Tampilkan log",
+ "hideLogs": "Sembunyikan log",
+ "deployLogs": "log deploy",
+ "logsWaiting": "Menunggu output…",
+ "logsEmpty": "Tidak ada output yang ditangkap.",
+ "back": "Kembali",
+ "phaseImages": "Mempersiapkan gambar",
+ "phaseServices": "Memulai layanan",
+ "phaseAppSetup": "Menyelesaikan pengaturan",
+ "phaseReady": "Aktif",
+ "phaseQueued": "Dalam antrean…",
+ "stepperTitle": "Langkah instalasi",
+ "firstLoginTitle": "Masuk pertama",
+ "firstLoginUser": "Nama pengguna",
+ "firstLoginPassword": "Password",
+ "destinationTitle": "Di mana menginstal",
+ "destinationHint": "Pilih server atau cloud tempat aplikasi ini berjalan.",
+ "descMore": "Lebih banyak",
+ "descLess": "Lebih sedikit",
+ "needsTitle": "Yang dibutuhkan",
+ "needsMemory": "{value} RAM",
+ "needsCpu": "{value}",
+ "hostFitTitle": "Server ini lebih kecil dari yang dibutuhkan {app}",
+ "hostFitMemory": "Membutuhkan {needed} RAM — ini memiliki {available}",
+ "hostFitCpu": "Membutuhkan {needed} — ini memiliki {available}",
+ "hostFitHint": "Instal di mesin yang lebih besar, atau pilih tujuan lain.",
+ "routePortLabel": "Hanya port (tanpa domain)",
+ "routePortDesc": "Dapat diakses di alamat host dan port — tanpa domain atau Cloud yang diperlukan.",
+ "routeFreeLabel": "Subdomain gratis",
+ "routeFreeDesc": "Subdomain gratis di {domain}, dirutekan oleh OpenShip Cloud.",
+ "routeFreeNeedsCloud": "Hubungkan OpenShip Cloud untuk menggunakan subdomain gratis.",
+ "routeCustomLabel": "Domain Anda sendiri",
+ "routeCustomDesc": "Arahkan domain yang Anda miliki ke aplikasi ini.",
+ "routeDomainLabel": "Domain",
+ "routeDomainDesc": "Akses di domain — subdomain gratis atau milik Anda sendiri.",
+ "exposeTitle": "Eksposur",
+ "httpBadge": "Web",
+ "tcpBadge": "Database",
+ "tcpPublishLabel": "Publikasikan port",
+ "tcpPublishDesc": "Publikasikan di host sehingga alat eksternal dapat terhubung.",
+ "tcpInternalLabel": "Hanya internal",
+ "tcpInternalDesc": "Hanya layanan lain di aplikasi ini yang dapat menjangkaunya.",
+ "tcpFirewallNote": "Buka port {port} di firewall server Anda untuk terhubung dari luar.",
+ "desktopReachNote": "Di desktop Anda bisa membukanya di localhost — atau teruskan port dari Server → Port untuk server remote.",
+ "statusInstalling": "Menginstal",
+ "statusLive": "Aktif",
+ "statusCancelled": "Dibatalkan",
+ "statusFailed": "Gagal",
+ "stopInstall": "Hentikan instalasi",
+ "stopping": "Menghentikan…",
+ "installCancelled": "Instalasi dibatalkan",
+ "startOver": "Mulai ulang",
+ "breadcrumbApps": "Aplikasi",
+ "appEyebrow": "Aplikasi",
+ "stopFailed": "Tidak dapat menghentikan instalasi",
+ "progressStep": "Langkah {current} dari {total}",
+ "progressServicesReady": "{done} dari {total} layanan siap",
+ "summaryTitle": "Konfigurasi",
+ "summaryDestination": "Tujuan",
+ "summaryServices": "Layanan",
+ "mail": {
+ "title": "Atur email",
+ "subtitle": "Deploy webmail di domain Anda sendiri, atau hubungkan mailbox yang ada.",
+ "chooseSelf": "Self-host server email",
+ "chooseSelfDesc": "SMTP/IMAP penuh + webmail (iRedMail) di server Anda — kami menyediakan semuanya.",
+ "chooseConnect": "Hubungkan mailbox yang ada",
+ "chooseConnectDesc": "Deploy hanya UI webmail, diarahkan ke penyedia SMTP/IMAP eksternal mana pun — Amazon SES, Oracle Cloud, Gmail, Fastmail, dan lainnya.",
+ "provider": "Penyedia",
+ "presets": {
+ "custom": "Kustom",
+ "ses": "Amazon SES",
+ "gmail": "Gmail",
+ "fastmail": "Fastmail"
+ },
+ "sendOnlyNote": "{provider} hanya mengirim email — tidak menyimpan mailbox. IMAP harus diarahkan ke tempat email Anda benar-benar disimpan.",
+ "sendOnlyNoInbox": "Belum ada inbox? Atur server email di sini, lalu teruskan pengiriman melalui {provider} →",
+ "imapOnOurServer": "Mailbox dibaca dari {host} — server email instance ini sendiri. Hanya pengiriman yang melalui {provider}.",
+ "relayNote": "Instance ini sudah menjalankan server email. Jika mailbox ada di sana, teruskan pengiriman dari tab Delivery → Sending: IMAP tetap lokal dan hanya pengiriman yang ke {provider}, per domain atau per alamat, dan webmail-nya tetap berfungsi.",
+ "relayCta": "Konfigurasikan relay sebagai gantinya →",
+ "hostname": "Alamat webmail",
+ "hostnameHint": "URL publik yang dibuka orang untuk membaca email mereka, misalnya mail.example.com.",
+ "imapHost": "Host IMAP",
+ "imapPort": "Port IMAP",
+ "smtpHost": "Host SMTP",
+ "smtpPort": "Port SMTP",
+ "deploy": "Deploy webmail",
+ "deploying": "Mendeploy…",
+ "hostnameRequired": "Masukkan alamat webmail.",
+ "hostsRequired": "Masukkan host IMAP dan SMTP."
+ }
+ },
+ "advanced": {
+ "projectInfo": {
+ "title": "Info Proyek",
+ "description": "Ringkasan konfigurasi saat ini"
+ },
+ "metric": {
+ "status": "Status",
+ "project": "Proyek",
+ "hostedOn": "Dihost di"
+ },
+ "projectMenu": {
+ "rename": "Ganti nama proyek",
+ "copyId": "Salin ID proyek",
+ "idCopied": "ID proyek disalin",
+ "copyFailed": "Tidak dapat menyalin ke clipboard",
+ "pause": "Jeda proyek",
+ "resume": "Lanjutkan proyek",
+ "delete": "Hapus proyek"
+ },
+ "rename": {
+ "title": "Ganti nama proyek",
+ "description": "Mengubah nama tampilan. Tidak ada yang di-deploy ulang.",
+ "label": "Nama proyek",
+ "placeholder": "Proyek saya",
+ "identityNote": "Proyek mempertahankan identitas \"{slug}\" — URL, nama container, dan volume tidak berubah. Untuk mengubah URL, edit domain di Domain & Route.",
+ "save": "Simpan",
+ "cancel": "Batal",
+ "toast": {
+ "renamed": "Diganti namanya menjadi {name}",
+ "failed": "Gagal mengganti nama proyek"
+ }
+ },
+ "statusActive": "Aktif",
+ "statusDisabled": "Nonaktif",
+ "hostedCloud": "OpenShip Cloud",
+ "hostedServer": "Server",
+ "hostedLocal": "Lokal",
+ "projectStatus": {
+ "title": "Status Proyek",
+ "description": "Kontrol apakah proyek aktif atau dijeda",
+ "active": "Proyek Aktif",
+ "disabled": "Proyek Nonaktif",
+ "liveAccessible": "Aktif dan dapat diakses",
+ "pausedInaccessible": "Dijeda dan tidak dapat diakses",
+ "disable": "Nonaktifkan",
+ "enable": "Aktifkan",
+ "migrating": "Migrasi",
+ "migratingHint": "Sedang dipindahkan ke server lain"
+ },
+ "migration": {
+ "title": "Migrasi",
+ "description": "Pindahkan proyek ini ke server lain",
+ "currentServer": "Server saat ini",
+ "migrate": "Migrasi",
+ "noOtherServers": "Tambah server kedua untuk memindahkan proyek ini ke salah satunya.",
+ "cloudLater": "Server ke server. Pindah ke atau dari OpenShip Cloud belum didukung.",
+ "startFailed": "Tidak dapat memulai migrasi",
+ "confirmTitle": "Pindahkan {project} ke {server}",
+ "confirmSubtitle": "Tidak ada yang berubah sampai Anda memulai pemindahan.",
+ "svcVolumes": "{count} volume",
+ "downtimeNote": "Proyek offline saat data disalin dan server baru memulainya. Layanan stateful mati selama seluruh pemindahan.",
+ "dnsNote": "DNS masih mengarah ke server lama — pindahkan record Anda setelah server baru melayani.",
+ "cancel": "Batal",
+ "confirmStart": "Mulai pindah",
+ "duplicate": "Duplikat",
+ "intentMove": "Pindahkan",
+ "intentCopy": "Duplikat",
+ "intentMoveHint": "Proyek dipindahkan. Container dan volume-nya dihapus dari server lama setelah Anda mengonfirmasi.",
+ "intentCopyHint": "Yang asli tetap berjalan. Proyek kedua dibuat di server baru dengan salinan data.",
+ "confirmTitleCopy": "Duplikat {project} ke {server}",
+ "confirmStartCopy": "Mulai duplikat",
+ "downtimeNoteCopy": "Proyek sebentar offline saat data disalin — volume harus berhenti agar bisa disalin dengan bersih. Ia mulai lagi di server lama segera setelah penyalinan selesai.",
+ "copyNoDomainsNote": "Salinan dimulai tanpa domain — yang asli tetap dengan proyek yang melayaninya.",
+ "fromLabel": "Dari",
+ "toLabel": "Ke",
+ "modeLabel": "Mode",
+ "scopeLabel": "Apa yang diduplikat",
+ "scopeAll": "Pilih semua",
+ "scopeHintAll": "Setiap layanan, beserta datanya.",
+ "scopeHintSome": "{count} layanan — sisanya hanya tetap di yang asli.",
+ "sourceServices": "{count} layanan",
+ "runningStatus": "Sedang berlangsung — {status}"
+ },
+ "cache": {
+ "title": "Cache",
+ "description": "Hapus artefak build yang di-cache dan instalasi dependensi",
+ "clearInstall": "Hapus Cache Instal",
+ "clearInstallDesc": "Hapus node_modules yang di-cache",
+ "clearBuild": "Hapus Cache Build",
+ "clearBuildDesc": "Hapus output build yang di-cache"
+ },
+ "transfer": {
+ "title": "Transfer & Clone",
+ "description": "Pindahkan atau duplikat proyek ini ke environment yang berbeda",
+ "current": "Saat ini",
+ "transfer": "Transfer",
+ "clone": "Clone",
+ "anotherServer": "Server lain",
+ "targetLocal": "Mesin Lokal",
+ "targetServer": "Server Self-Hosted",
+ "targetCloud": "OpenShip Cloud"
+ },
+ "danger": {
+ "title": "Zona Bahaya",
+ "description": "Aksi tak terbalikkan untuk proyek ini",
+ "body": "Menghapus proyek ini akan menghapus secara permanen semua deployment, domain kustom, environment variable, dan data analitik.",
+ "delete": "Hapus Proyek"
+ },
+ "toast": {
+ "toggleFailed": "Gagal mengalihkan proyek",
+ "clearInstallFailed": "Gagal menghapus cache instal",
+ "clearBuildFailed": "Gagal menghapus cache build"
+ },
+ "routing": {
+ "title": "Routing",
+ "description": "Cara edge menjangkau upstream aplikasi ini",
+ "intro": "Pilih cara edge mengalamatkan container proyek ini.",
+ "note": "Loopback port direkomendasikan: port host 127.0.0.1 pribadi, stabil melintasi restart dan tidak pernah terekspos jaringan. Container IP adalah lanjutan (pertukaran zero-downtime; perlu edge di Docker bridge; tidak didukung di Docker Desktop).",
+ "modes": {
+ "auto": {
+ "label": "Otomatis",
+ "desc": "Direkomendasikan (loopback port)"
+ },
+ "loopbackPort": {
+ "label": "Loopback port",
+ "desc": "Port host 127.0.0.1 pribadi"
+ },
+ "containerIp": {
+ "label": "IP Container",
+ "desc": "Lanjutan — bridge IP"
+ }
+ },
+ "toast": {
+ "saved": "Routing disetel ke {mode}",
+ "failed": "Gagal memperbarui routing"
+ }
+ },
+ "internalAlias": {
+ "title": "Hostname internal",
+ "description": "Nama kustom yang digunakan layanan lain untuk menjangkau aplikasi ini secara pribadi",
+ "intro": "Atur nama DNS tambahan untuk aplikasi ini di jaringan proyek pribadinya. Ia resolve bersama default (slug proyek) — layanan tertaut lain bisa menggunakan keduanya. Akses publik tidak berubah; ini tidak pernah mengekspos port.",
+ "label": "Alias hostname",
+ "hint": "Huruf, angka, dan tanda hubung saja. Kosongkan untuk menggunakan slug proyek.",
+ "save": "Simpan",
+ "toast": {
+ "saved": "Hostname internal diperbarui",
+ "failed": "Tidak dapat memperbarui hostname internal",
+ "invalid": "Masukkan setidaknya satu huruf atau angka"
+ }
+ }
+ },
+ "routeRules": {
+ "title": "Aturan route",
+ "description": "Aturan rate-limit, blokir, dan izin/tolak yang diterapkan di edge — diterapkan secara instan, tanpa deploy ulang.",
+ "loading": "Memuat aturan…",
+ "empty": "Belum ada aturan route. Tambahkan satu di bawah.",
+ "allHosts": "Semua hostname",
+ "allPaths": "Semua path",
+ "addTitle": "Tambah aturan",
+ "pathLabel": "Prefix path (opsional)",
+ "rps": "Request / detik",
+ "burst": "Burst",
+ "methods": "Izinkan hanya metode",
+ "banIps": "Blokir IP / CIDR",
+ "banCountries": "Blokir negara (ISO)",
+ "allowCountries": "Izinkan hanya negara (ISO)",
+ "denyCidrs": "Tolak CIDR",
+ "allowReferers": "Hotlink: izinkan referer",
+ "banUserAgents": "Blokir user-agent (substring)",
+ "blockEmptyUa": "Blokir user-agent kosong",
+ "blockStatus": "Blokir kode respons",
+ "add": "Tambah aturan",
+ "remove": "Hapus",
+ "enabled": "Aktif",
+ "disabled": "Nonaktif",
+ "saved": "Aturan disimpan",
+ "saveFailed": "Gagal menyimpan aturan",
+ "loadFailed": "Gagal memuat aturan"
+ },
+ "backup": {
+ "live": {
+ "title": "Backup langsung",
+ "description": "Progres real-time untuk jalankan yang baru Anda mulai. Bertahan penyegaran halaman.",
+ "dismiss": "Tutup"
+ },
+ "destinations": {
+ "title": "Tujuan backup",
+ "description": "Target penyimpanan per pengguna. Tambah bucket S3-compatible, server SFTP, atau disk lokal.",
+ "manage": "Kelola",
+ "loading": "Memuat…",
+ "emptyPrefix": "Belum ada tujuan. ",
+ "emptyLink": "Buat satu",
+ "emptySuffix": " untuk mulai mencadangkan.",
+ "verified": "Terverifikasi",
+ "failed": "Gagal"
+ },
+ "services": {
+ "title": "Layanan + kebijakan",
+ "description": "Setiap layanan bisa memiliki kebijakan backup sendiri (jadwal + tujuan). Default level proyek ada di Bagian 2.",
+ "refresh": "Segarkan",
+ "empty": "Proyek ini belum memiliki layanan. Backup level layanan muncul di sini setelah Anda menambahkannya.",
+ "policyLabel": "Kebijakan:",
+ "manualOnly": "hanya manual",
+ "preDeploy": "pra-deploy",
+ "webhook": "webhook",
+ "noPolicy": "Tanpa kebijakan",
+ "backupNow": "Backup sekarang",
+ "editPolicy": "Edit kebijakan",
+ "addDestinationFirst": "Tambah tujuan terlebih dahulu",
+ "createPolicyHint": "Buat kebijakan backup untuk layanan ini",
+ "createPolicy": "Buat kebijakan"
+ },
+ "recent": {
+ "title": "Backup terbaru",
+ "description": "25 jalankan terbaru di semua layanan di proyek ini.",
+ "loading": "Memuat…",
+ "empty": "Belum ada backup.",
+ "protectedTitle": "Dilindungi dari pruning retention",
+ "protected": "Dilindungi",
+ "restoreTitle": "Pulihkan dari backup ini",
+ "allowPrune": "Izinkan retention memangkas",
+ "protectFrom": "Lindungi dari retention"
+ },
+ "toast": {
+ "runFailed": "Jalankan backup gagal",
+ "toggleProtectionFailed": "Gagal mengalihkan perlindungan"
+ }
+ },
+ "build": {
+ "edit": "Edit",
+ "webmail": {
+ "title": "Dikelola oleh openship",
+ "description": "Webmail menggunakan pipeline build dan mulai yang tetap — command instal, build, dan run tidak dapat dikonfigurasi. Deploy ulang dari ringkasan email untuk mengambil perubahan upstream."
+ },
+ "services": {
+ "title": "Pengaturan per layanan",
+ "descriptionTemplate": "Proyek ini memiliki {serviceLabel}. Command build, framework, port, dan command run ada di setiap baris layanan — lihat dan edit di tab Layanan.",
+ "open": "Buka Layanan",
+ "subAppOne": "{count} sub-aplikasi",
+ "subAppOther": "{count} sub-aplikasi",
+ "composeOne": "{count} compose service",
+ "composeOther": "{count} compose service",
+ "both": "{subApps} dan {composeServices}"
+ },
+ "runtime": {
+ "title": "Konfigurasi runtime",
+ "description": "Hanya-baca. Edit melalui wizard deploy sehingga setiap perubahan dikirim sebagai versi baru.",
+ "framework": "Framework",
+ "packageManager": "Manajer paket",
+ "runtimeIsolation": "Isolasi runtime",
+ "runtimePort": "Port runtime",
+ "installCommand": "Command instal",
+ "buildCommand": "Command build",
+ "noBuildStep": "Tanpa langkah build",
+ "outputDirectory": "Direktori output",
+ "rootDirectory": "Direktori root",
+ "composePath": "File Compose",
+ "startCommand": "Command mulai",
+ "staticNoServer": "Statik (tanpa server)",
+ "modeSandboxed": "Sandboxed (container)",
+ "modeDirect": "Direct (host process)",
+ "modeDefault": "Default (diselesaikan saat deploy)",
+ "modeStatic": "Tidak ada — dilayani sebagai file statik"
+ },
+ "storage": {
+ "title": "Penyimpanan persisten",
+ "description": "Path yang dipertahankan saat versi baru menggantikan yang lama. Sisanya di container dibuang saat deploy.",
+ "edit": "Edit",
+ "save": "Simpan",
+ "cancel": "Batal",
+ "saved": "Penyimpanan diperbarui. Berlaku saat deploy berikutnya.",
+ "reset": "Gunakan default framework",
+ "empty": "Tidak ada yang dipertahankan antar deploy.",
+ "inherited": "Default framework untuk stack ini.",
+ "declared": "Ditetapkan untuk proyek ini.",
+ "placeholder": "storage\nuploads:/app/public/uploads",
+ "hint": "Satu per baris. Path kosong relatif terhadap aplikasi (storage), atau berikan mount lengkap (name:/app/path, /host/path:/app/path)."
+ },
+ "objectStorage": {
+ "title": "Penyimpanan objek",
+ "description": "Bucket S3 untuk unggahan — bertahan deploy ulang dan bekerja dengan lebih dari satu replika, berbeda dengan path lokal.",
+ "connect": "Hubungkan",
+ "disconnect": "Putuskan",
+ "disconnected": "Penyimpanan objek terputus.",
+ "connected": "Bucket terverifikasi dan tersambung. Berlaku saat deploy berikutnya.",
+ "loading": "Memeriksa…",
+ "empty": "Tidak ada bucket yang tersambung. Unggahan tetap di disk container.",
+ "sourceLabel": "Sumber",
+ "externalOption": "Penyedia eksternal",
+ "providerLabel": "Penyedia",
+ "bucketLabel": "Bucket",
+ "endpointLabel": "Endpoint",
+ "regionLabel": "Region",
+ "accessKeyLabel": "ID access key",
+ "secretKeyLabel": "Secret access key",
+ "testAndConnect": "Uji & hubungkan",
+ "cancel": "Batal",
+ "envLabel": "Variabel",
+ "envHint": "Menetapkan:",
+ "appliesOnDeploy": "Diterapkan saat deploy berikutnya."
+ },
+ "env": {
+ "title": "Environment variable",
+ "description": "Dienkripsi saat penyimpanan. Secret tetap tersembunyi — hanya nilai yang Anda ubah yang ditulis.",
+ "edit": "Edit"
+ }
+ },
+ "envVars": {
+ "cancel": "Batal",
+ "saveChanges": "Simpan perubahan",
+ "close": "Tutup",
+ "applyTitle": "Terapkan tanpa rebuild",
+ "applyText": "Disimpan. Terapkan ini ke layanan yang berjalan — ia restart dengan env baru, tanpa clone atau rebuild.",
+ "applyButton": "Terapkan (restart, tanpa rebuild)",
+ "title": "Environment variable",
+ "description": "Produksi. Dienkripsi saat penyimpanan. Secret ditampilkan sebagai sudah diatur — ketik untuk mengganti; kosongkan untuk mempertahankan.",
+ "empty": "Belum ada environment variable.",
+ "keyPlaceholder": "KUNCI",
+ "secretPlaceholder": "•••••••• (sudah diatur — ketik untuk mengganti)",
+ "valuePlaceholder": "nilai",
+ "hideValue": "Sembunyikan nilai",
+ "showValue": "Tampilkan nilai",
+ "markedSecret": "Ditandai sebagai secret",
+ "markSecret": "Tandai sebagai secret",
+ "removeVariable": "Hapus variabel",
+ "addVariable": "Tambah variabel",
+ "toast": {
+ "loadFailed": "Gagal memuat environment variable",
+ "loadFailedTitle": "Kesalahan",
+ "validationTitle": "Validasi",
+ "saved": "Environment variable disimpan",
+ "savedTitle": "Disimpan",
+ "saveFailed": "Gagal menyimpan environment variable",
+ "saveFailedTitle": "Gagal menyimpan",
+ "noChanges": "Tidak ada perubahan untuk disimpan — nilai yang disimpan dipertahankan.",
+ "noChangesTitle": "Tidak ada yang perlu disimpan",
+ "applying": "Memulai ulang layanan dengan env baru — tanpa rebuild.",
+ "applyingTitle": "Menerapkan",
+ "applyFailed": "Tidak dapat menerapkan env ke layanan yang berjalan",
+ "applyFailedTitle": "Gagal menerapkan"
+ },
+ "linkedBadge": "Tertaut",
+ "linkedManaged": "melalui {app}",
+ "linkedTitle": "Integrasi tertaut",
+ "linkedBody": "Nilai ini berasal dari koneksi {app}. Kelola dari aplikasi itu — perubahan yang dibuat di sini tidak akan bertahan.",
+ "unlink": "Hapus koneksi"
+ },
+ "gitInfo": {
+ "autoDeploy": "Auto-deploy"
+ },
+ "git": {
+ "connectFirst": {
+ "title": "Hubungkan GitHub terlebih dahulu",
+ "description": "Tautkan akun GitHub Anda untuk menghubungkan repository ke proyek ini.",
+ "connecting": "Menghubungkan…",
+ "connect": "Hubungkan GitHub"
+ },
+ "link": {
+ "title": "Tautkan Repository",
+ "description": "Hubungkan repository GitHub untuk mengaktifkan deployment yang dipicu webhook.",
+ "select": "Pilih Repository"
+ },
+ "picker": {
+ "title": "Pilih repository",
+ "description": "Pilih repository untuk ditautkan ke proyek ini",
+ "linking": "Menautkan repository…"
+ },
+ "appBanner": {
+ "title": "Aplikasi GitHub belum terinstal",
+ "description": "Instal Aplikasi GitHub OpenShip di akun atau organisasi Anda untuk mengaktifkan auto-deploy dan deployment yang dipicu webhook untuk proyek cloud.",
+ "install": "Instal Aplikasi GitHub"
+ },
+ "webhookBanner": {
+ "title": "Aktifkan auto-deploy",
+ "description": "Auto-deploy memerlukan endpoint webhook langsung. Ekspos API OpenShip ini di URL publik atau konfigurasi domain webhook yang terverifikasi untuk pengiriman langsung."
+ },
+ "source": {
+ "title": "Repository Sumber",
+ "description": "Integrasi Git, pemicu deployment, dan kesehatan repository",
+ "repository": "Repository",
+ "open": "Buka"
+ },
+ "webhookEndpoint": {
+ "title": "Endpoint Webhook",
+ "descriptionSet": "GitHub mengirim event push langsung ke domain ini",
+ "descriptionUnset": "Pilih domain untuk pengiriman webhook langsung",
+ "updating": "Memperbarui…",
+ "select": "Pilih domain…",
+ "clear": "Hapus pilihan"
+ },
+ "autoDeployCard": {
+ "title": "Auto Deploy",
+ "enabled": "Aktif",
+ "disabled": "Nonaktif",
+ "descDelivers": "Push dikirim ke {domain}",
+ "descAuto": "Push memicu deployment secara otomatis",
+ "descManual": "Deployment harus dimulai secara manual"
+ },
+ "webhookCard": {
+ "title": "Webhook",
+ "direct": "Langsung",
+ "active": "Aktif",
+ "inactive": "Tidak aktif",
+ "descDirect": "Event dikirim langsung melalui {domain}",
+ "descActive": "Event repository mencapai OpenShip",
+ "descInactive": "Webhook belum dikonfigurasi atau tidak merespons"
+ },
+ "rollbackStrategy": {
+ "title": "Strategi rollback",
+ "rebuild": "Rebuild (git)",
+ "instant": "Instan (snapshot)",
+ "descRebuild": "Membangun ulang commit lama, kecuali gambar-nya masih ada di host.",
+ "descInstant": "Menyimpan gambar setiap versi, sehingga pemulihan tidak pernah membangun ulang. Menggunakan lebih banyak disk.",
+ "toggleAria": "Alihkan retensi snapshot",
+ "keepNone": "Tidak menyimpan salinan ekstra",
+ "keepCopies": "Simpan salinan per versi",
+ "descRebuildFiles": "Membangun ulang commit lama, kecuali file-nya masih ada di host.",
+ "descInstantFiles": "Menyimpan file setiap versi, sehingga pemulihan tidak pernah membangun ulang. Menggunakan lebih banyak disk."
+ },
+ "rollbackHistory": {
+ "title": "Riwayat rollback",
+ "valueOne": "{count} versi",
+ "valueOther": "{count} versi",
+ "descSnapshot": "Versi lebih lama dipangkas untuk mengembalikan disk.",
+ "descGit": "Gambar terbaru dipertahankan bagaimanapun, sehingga rollback bisa melewati build.",
+ "decreaseAria": "Kurangi riwayat rollback",
+ "increaseAria": "Tambah riwayat rollback",
+ "sizeEach": "~{size} masing-masing",
+ "diskFree": "{size} tersedia",
+ "autoBadge": "dibuat secara otomatis",
+ "manualBadge": "ditetapkan secara manual",
+ "kindFiles": "file build di disk",
+ "kindImages": "gambar di host",
+ "descGitFiles": "File versi terbaru dipertahankan bagaimanapun, sehingga rollback bisa melewati build."
+ },
+ "commits": {
+ "title": "Commit Terbaru",
+ "subtitle": "Commit terbaru di {branch}",
+ "empty": "Tidak ada commit yang ditemukan",
+ "emptyDesc": "Push ke repository Anda dan aktivitas commit terbaru akan muncul di sini.",
+ "viewAll": "Lihat semua commit di GitHub"
+ },
+ "cloneToken": {
+ "title": "Override Token Clone",
+ "description": "Token berskala proyek yang digunakan modul clone sebelum kembali ke token global Anda atau Aplikasi GitHub",
+ "loading": "Memuat…",
+ "explainer": "Tempel GitHub PAT di sini untuk digunakan HANYA untuk proyek ini. Prioritas tertinggi dalam rantai clone — mengganti token level pengaturan Anda dan Aplikasi GitHub.",
+ "hide": "Sembunyikan token",
+ "show": "Tampilkan token",
+ "saveToken": "Simpan token",
+ "cancel": "Batal",
+ "savedTitle": "Token disimpan untuk proyek ini",
+ "lastUpdated": "Terakhir diperbarui {when}",
+ "justNow": "baru saja",
+ "replace": "Ganti",
+ "clear": "Hapus"
+ },
+ "toast": {
+ "pasteToken": "Tempel token sebelum menyimpan",
+ "cloneTokenTitle": "Token clone",
+ "tokenSaved": "Token clone proyek disimpan",
+ "tokenSaveFailed": "Gagal menyimpan token",
+ "tokenCleared": "Token clone proyek dihapus",
+ "tokenClearFailed": "Gagal menghapus token",
+ "autoDeployEnabled": "Auto-deploy diaktifkan",
+ "autoDeployDisabled": "Auto-deploy dinonaktifkan",
+ "autoDeployFailed": "Gagal mengalihkan auto-deploy",
+ "rollbackGit": "Strategi rollback: Git-driven (tanpa snapshot)",
+ "rollbackSnapshot": "Strategi rollback: Snapshot (artefak dipertahankan)",
+ "rollbackStrategyFailed": "Gagal memperbarui strategi rollback",
+ "rollbackWindowOne": "Menyimpan {count} versi terakhir untuk rollback",
+ "rollbackWindowOther": "Menyimpan {count} versi terakhir untuk rollback",
+ "rollbackHistoryFailed": "Gagal memperbarui riwayat rollback",
+ "webhookDomainSet": "Domain webhook disetel ke {domain}",
+ "webhookDomainCleared": "Domain webhook dihapus",
+ "webhookDomainFailed": "Gagal menyetel domain webhook",
+ "linked": "Ditautkan ke {repo}",
+ "appNotInstalled": "Aplikasi GitHub belum terinstal untuk akun ini",
+ "linkFailed": "Gagal menautkan repository"
+ }
+ },
+ "logs": {
+ "noRuntime": "Tidak ada log runtime",
+ "noRuntimeDesc": "Proyek ini tidak memiliki server process yang berjalan dan tidak ada domain, jadi tidak ada yang bisa di-streaming. Tambah domain dan log request edge-nya akan muncul di sini.",
+ "terminal": "Terminal",
+ "server": "Server",
+ "requests": "Request",
+ "target": "Target log runtime",
+ "targetDescProject": "Beralih antara runtime proyek dan runtime layanan.",
+ "targetDescService": "Beralih antara runtime layanan.",
+ "projectRuntime": "Runtime proyek",
+ "selectService": "Pilih layanan untuk melihat log runtime-nya.",
+ "projectFallback": "Proyek"
+ },
+ "resources": {
+ "title": "Daya Mesin",
+ "description": "Batasan CPU dan memori untuk container proyek ini.",
+ "loading": "Membaca mesin target…",
+ "machineCapacity": "Mesin: {cpu} vCPU · {memory}",
+ "exceedsMachine": "Melebihi mesin ini",
+ "appliesOnNextDeploy": "Batasan diterapkan saat container dibuat ulang — deploy ulang agar perubahan berlaku.",
+ "tiers": {
+ "unlimited": {
+ "name": "Tanpa batas",
+ "description": "Gunakan seluruh mesin (default)",
+ "spec": "Kapasitas mesin"
+ },
+ "micro": {
+ "name": "Micro",
+ "description": "Sidecar dan worker kecil"
+ },
+ "low": {
+ "name": "Rendah",
+ "description": "Aplikasi kecil dan situs statis"
+ },
+ "medium": {
+ "name": "Sedang",
+ "description": "Sebagian besar aplikasi web"
+ },
+ "high": {
+ "name": "Tinggi",
+ "description": "Aplikasi dengan lalu lintas tinggi"
+ },
+ "xlarge": {
+ "name": "Ekstra besar",
+ "description": "Database dan workload berat"
+ }
+ },
+ "custom": {
+ "name": "Kustom",
+ "description": "Atur batasan Anda sendiri",
+ "notSet": "Belum ditetapkan"
+ },
+ "customPanel": {
+ "boundedBy": "Mesin ini memiliki {cpu} vCPU dan {memory}. Masukkan 0 untuk tanpa batas.",
+ "capacityUnknown": "Tidak dapat membaca kapasitas mesin ini, jadi tidak ada batas yang diterapkan. Masukkan 0 untuk tanpa batas.",
+ "cpuCores": "Inti CPU",
+ "memory": "Memori (MB)",
+ "zeroMeansNoLimit": "0 = tanpa batas",
+ "cancel": "Batal",
+ "saving": "Menyimpan…",
+ "save": "Simpan"
+ },
+ "toast": {
+ "updated": "Daya mesin diperbarui",
+ "updateFailed": "Gagal memperbarui daya mesin",
+ "invalidValues": "CPU dan memori harus 0 atau bilangan positif"
+ }
+ },
+ "sleep": {
+ "title": "Mode Mesin",
+ "description": "Kontrol ketersediaan dan efisiensi biaya",
+ "autoSleep": "Tidur Otomatis",
+ "recommended": "DIREKOMENDASIKAN",
+ "autoSleepDesc": "Berhenti saat idle, bangun secara instan",
+ "autoSleepMeta": "Efisien biaya • Tanpa cold start",
+ "alwaysOn": "Selalu Aktif",
+ "alwaysOnDesc": "Container tidak pernah berhenti",
+ "alwaysOnMeta": "Ketersediaan maksimum • Biaya lebih tinggi",
+ "infoTitle": "Cara kerja Tidur Otomatis",
+ "infoBefore": "Saat ada request, container Anda tetap berjalan. Setelah tidak aktif, ia tidur untuk menghemat biaya. Saat request berikutnya, ia bangun ",
+ "infoEmphasis": "secara instan",
+ "infoAfter": " — tanpa penundaan cold start.",
+ "toast": {
+ "updated": "Mode tidur berhasil diperbarui",
+ "updateFailed": "Gagal memperbarui mode tidur"
+ }
+ },
+ "deleteDialog": {
+ "title": "Hapus Proyek",
+ "bodyPrefix": "Apakah Anda yakin ingin menghapus ",
+ "bodySuffix": "? Aksi ini tidak dapat dibatalkan dan akan menghapus secara permanen semua data proyek, deployment, dan pengaturan.",
+ "cancel": "Batal",
+ "delete": "Hapus Proyek"
+ },
+ "deletion": {
+ "title": "Hapus proyek",
+ "cannotUndo": "Aksi ini tidak dapat dibatalkan.",
+ "aboutToPrefix": "Anda akan menghapus ",
+ "aboutToSuffix": ".",
+ "scanning": "Memindai layanan dan volume yang terlampir…",
+ "wipeAria": "Juga hapus data layanan",
+ "wipeLabelOne": "Juga hapus data layanan ({count} volume)",
+ "wipeLabelOther": "Juga hapus data layanan ({count} volume)",
+ "wipeOn": "Volume akan dihapus secara permanen — konten database, cache, unggahan, semua yang disimpan oleh layanan ini akan hilang.",
+ "wipeOff": "Nonaktif: container dihapus tetapi volume bernama tetap di disk. Lampirkan kembali nanti berdasarkan nama.",
+ "appData": "Data aplikasi",
+ "volOne": "{count} vol",
+ "volOther": "{count} vol",
+ "amberWipe": "Semua deployment, domain, env var, dan data layanan persisten akan dihapus secara permanen.",
+ "amberNoWipe": "Semua deployment, domain, env var, dan analitik akan dihapus secara permanen.",
+ "typePrefix": "Ketik ",
+ "typeSuffix": " untuk mengonfirmasi.",
+ "cancel": "Batal",
+ "confirmDeleteEnvWipe": "Hapus environment + hapus data",
+ "confirmDeleteEnv": "Hapus environment",
+ "recordOnlyLabel": "Hapus dari OpenShip saja",
+ "recordOnlyDesc": "Mempertahankan aplikasi dan data-nya berjalan di server Anda — hanya catatan OpenShip yang dihapus. Anda bisa mengimpornya kembali nanti.",
+ "recordOnlyReversible": "Dapat dibalik — aplikasi tetap di server Anda.",
+ "recordOnlyNote": "Tidak ada di server yang disentuh — aplikasi tetap berjalan dengan datanya. Hanya catatan OpenShip yang dihapus, dan dapat diimpor kembali nanti.",
+ "confirmRecordOnly": "Hapus catatan",
+ "linkedTitleOne": "{count} proyek terhubung ke aplikasi ini",
+ "linkedTitleOther": "{count} proyek terhubung ke aplikasi ini",
+ "linkedNote": "Mereka tetap berjalan dengan data yang tidak tersentuh — mereka hanya kehilangan koneksi ini. Deploy ulang mereka untuk mengambilnya.",
+ "linkedNoteRecordOnly": "Mereka tetap berjalan, tetapi tautan ikut dengan catatan — mengimpor ulang aplikasi ini nanti tidak akan mengembalikannya."
+ },
+ "routing": {
+ "title": "Routing",
+ "description": "Rewrite, redirect & header. Diterapkan saat disimpan — tanpa rebuild.",
+ "saving": "Menyimpan…",
+ "save": "Simpan",
+ "summaryNone": "Tidak ada yang dikonfigurasi",
+ "summaryActive": "Dikonfigurasi",
+ "toast": {
+ "title": "Routing",
+ "updated": "Routing diperbarui — diterapkan ke deployment yang berjalan.",
+ "failed": "Gagal memperbarui routing"
+ }
+ },
+ "domains": {
+ "status": {
+ "verified": "Terverifikasi",
+ "active": "Aktif",
+ "failed": "Gagal",
+ "removing": "Menghapus",
+ "pending": "Tertunda"
+ },
+ "ssl": {
+ "includedByHost": "Disertakan oleh host",
+ "active": "Aktif",
+ "provisioning": "Menyediakan",
+ "expired": "Kedaluwarsa",
+ "error": "Kesalahan",
+ "inactive": "Tidak aktif",
+ "external": "TLS eksternal",
+ "manual": "Sertifikat manual"
+ },
+ "typeCustom": "Domain kustom",
+ "typeFree": "Subdomain gratis",
+ "primaryDomainTitle": "Domain utama",
+ "domainNTitle": "Domain {n}",
+ "portLabel": "Port {port}",
+ "noPortSelected": "Tidak ada port yang dipilih",
+ "meta": {
+ "accessTitle": "URL Akses",
+ "accessSubtitle": "Endpoint pengembangan lokal",
+ "local": "Lokal",
+ "availableOnMachine": "Tersedia di mesin ini",
+ "primaryTitle": "Domain Utama",
+ "primaryAcross": "Route utama di {count} domain",
+ "hostManaged": "URL produksi yang dikelola host",
+ "customProduction": "Domain produksi kustom"
+ },
+ "route": {
+ "disabled": "Nonaktif",
+ "routePaused": "Route dijeda",
+ "serviceDisabled": "Layanan nonaktif",
+ "internal": "Internal",
+ "notExposed": "Tidak terekspos",
+ "public": "Publik"
+ },
+ "actions": {
+ "visit": "Kunjungi",
+ "editDomains": "Edit domain",
+ "hideSetup": "Sembunyikan pengaturan",
+ "addDomain": "Tambah domain"
+ },
+ "add": {
+ "title": "Tambah domain",
+ "description": "Tambah route: pilih domain gratis atau kustom dan port yang dipetakan.",
+ "free": "Subdomain gratis",
+ "custom": "Domain kustom",
+ "domainName": "Nama domain",
+ "subdomain": "Subdomain",
+ "customPlaceholder": "yourdomain.com",
+ "defaultAppName": "my-app",
+ "mapsToPort": "Dipetakan ke port",
+ "servesPath": "Melayani path",
+ "servesPathHint": "Request ke domain ini dilayani dari path ini di output build Anda.",
+ "servesPathRedeploy": "Path baru berlaku saat deploy berikutnya.",
+ "includeWww": "Sertakan www",
+ "includeWwwDesc": "Juga buat record untuk www.{domain}",
+ "includeWwwFallback": "yourdomain.com",
+ "externalIngress": "Ingress & TLS eksternal",
+ "externalIngressDesc": "TLS berakhir di upstream (Cloudflare Tunnel, load balancer). Verifikasi hanya dengan TXT — domain tidak perlu mengarah ke server ini, dan kami tidak akan menerbitkan sertifikat.",
+ "noWww": "Domain itu sudah ada di proyek ini.",
+ "adding": "Menambah…",
+ "submit": "Tambah domain"
+ },
+ "dns": {
+ "title": "Record DNS",
+ "descPreview": "Tambah record ini di penyedia DNS Anda, lalu klik Tambah domain untuk melampirkannya",
+ "descApply": "Terapkan record ini di penyedia DNS Anda, lalu tunggu propagasi",
+ "infoPreview": "Tambah record ini terlebih dahulu — tidak berubah setelah ditambah, jadi propagasi dimulai sekarang. Lalu tekan Tambah domain untuk melampirkannya, dan akhirnya Verifikasi setelah DNS resolve.",
+ "infoApply": "Perubahan DNS bisa memakan waktu hingga 48 jam untuk dipropagasi secara global. Setelah record resolve, klik Verifikasi di bawah — kami akan memeriksa DNS, menandai domain aktif, dan menyediakan SSL di latar belakang.",
+ "verifying": "Memverifikasi…",
+ "verify": "Verifikasi {hostname}"
+ },
+ "records": {
+ "toggle": "Record DNS",
+ "hint": "Tambah ini di penyedia DNS Anda, lalu klik Verifikasi.",
+ "loading": "Memuat record…",
+ "none": "Tidak ada record untuk ditambahkan untuk domain ini.",
+ "failed": "Tidak dapat memuat record DNS untuk domain ini.",
+ "retry": "Coba lagi"
+ },
+ "verifyMissing": {
+ "both": "CNAME maupun record TXT belum resolve — tamb record di bawah, tunggu DNS propagasi, lalu Verifikasi.",
+ "cname": "Record CNAME belum resolve — tambah di bawah, tunggu DNS propagasi, lalu Verifikasi.",
+ "txt": "Record verifikasi TXT belum resolve — tambah di bawah, tunggu DNS propagasi, lalu Verifikasi."
+ },
+ "portHint": {
+ "body": "Tidak ada yang merespons di port {port} — request mungkin 502 sampai aplikasi mendengarkan di sana.",
+ "bodyService": "Layanan \"{service}\": tidak ada yang merespons di port {port} — request mungkin 502 sampai ia mendengarkan di sana."
+ },
+ "outputHint": {
+ "body": "Tidak ada output build yang ditemukan di {path} — periksa Direktori Output Anda dan deploy ulang.",
+ "noIndex": "Output ada di {path} tetapi tidak ada file index — request akan 404. Periksa Direktori Output Anda.",
+ "notServed": "Edge merespons HTTP {status} untuk {path} — path ini tidak melayani. Periksa Direktori Output dan route Anda."
+ },
+ "overview": {
+ "primary": "Utama",
+ "mappedTo": "Dipetakan ke",
+ "redirect": "Redirect",
+ "redirectValue": "{status} → {hostname}",
+ "status": "Status",
+ "ssl": "SSL",
+ "visit": "Kunjungi"
+ },
+ "cold": {
+ "localUrl": "URL Lokal",
+ "type": "Tipe",
+ "status": "Status"
+ },
+ "edit": {
+ "title": "Edit domain",
+ "descServer": "Edit port internal mana yang harus dirutekan setiap domain.",
+ "descStatic": "Edit path statik mana yang harus dilayani setiap domain.",
+ "cancel": "Batal",
+ "saving": "Menyimpan…",
+ "save": "Simpan perubahan"
+ },
+ "addRoute": {
+ "cancel": "Batal",
+ "add": "Tambah route",
+ "free": "Subdomain gratis",
+ "custom": "Domain kustom",
+ "customPlaceholder": "app.example.com",
+ "defaultServiceName": "my-service",
+ "port": "Port",
+ "portPlaceholder": "8080",
+ "submit": "Tambah",
+ "loading": "Memuat route…",
+ "emptyPrefix": "Belum ada domain. Klik ",
+ "emptyAction": "Tambah route",
+ "emptySuffix": " untuk mengarahkan domain ke port layanan."
+ },
+ "editRoute": {
+ "title": "Edit route",
+ "internalOnly": "Hanya internal",
+ "close": "Tutup",
+ "disabledWarning": "Layanan dinonaktifkan - route tidak aktif sampai layanan diaktifkan kembali.",
+ "save": "Simpan perubahan"
+ },
+ "menu": {
+ "editRoute": "Edit route",
+ "settingPrimary": "Menyetel utama…",
+ "setPrimary": "Tetapkan sebagai utama",
+ "verifying": "Memverifikasi…",
+ "verify": "Verifikasi",
+ "renewing": "Memperbarui…",
+ "renewSsl": "Perbarui SSL",
+ "rechecking": "Memeriksa ulang…",
+ "recheckSsl": "Periksa ulang SSL",
+ "uploadCert": "Unggah sertifikat"
+ },
+ "certUpload": {
+ "title": "Unggah sertifikat",
+ "desc": "Layani TLS dari sertifikat Anda sendiri (milik sendiri atau Cloudflare Origin CA) alih-alih Let's Encrypt. Gunakan ini saat proxy upstream menampilkan domain ini dan origin masih memerlukan sertifikat nyata (Cloudflare Full-strict). Certbot dinonaktifkan untuk domain ini setelah sertifikat diunggah — perbarui dengan mengunggah yang baru.",
+ "certLabel": "Sertifikat (fullchain PEM)",
+ "certPlaceholder": "-----BEGIN CERTIFICATE-----",
+ "keyLabel": "Kunci privat (PEM)",
+ "keyPlaceholder": "-----BEGIN PRIVATE KEY-----",
+ "submit": "Instal sertifikat",
+ "submitting": "Menginstal…",
+ "close": "Batal"
+ },
+ "toast": {
+ "enterPort": "Masukkan port yang harus dirutekan domain ini (1–65535).",
+ "addDomainTitle": "Tambah domain",
+ "addDomainFailed": "Gagal menambah domain",
+ "addDomainFailedTitle": "Gagal menambah domain",
+ "addedCustom": "{label} ditambah — terapkan record DNS, lalu Verifikasi.",
+ "addedFree": "{label} ditambah.",
+ "copied": "Disalin ke clipboard",
+ "verifiedSuccess": "{hostname} terverifikasi — SSL sedang disediakan di latar belakang.",
+ "verifiedTitle": "Domain terverifikasi",
+ "verifyNotYet": "{hostname} belum bisa diverifikasi.",
+ "verifyFailedTitle": "Verifikasi gagal",
+ "verifyFailed": "Gagal memverifikasi domain.",
+ "sslRenewed": "SSL diperbarui untuk {hostname}.",
+ "sslRenewFailed": "Gagal memperbarui SSL untuk {hostname}.",
+ "sslTitle": "SSL",
+ "sslVerified": "SSL terverifikasi untuk {hostname}.",
+ "sslNoCert": "Belum ditemukan sertifikat yang valid untuk {hostname}. Jika baru deploy, beri waktu untuk Let's Encrypt, lalu periksa ulang.",
+ "sslRecheckFailed": "Gagal memeriksa ulang SSL untuk {hostname}.",
+ "certUploaded": "Sertifikat terinstal untuk {hostname}.",
+ "certUploadFailed": "Gagal menginstal sertifikat untuk {hostname}.",
+ "completeEndpoints": "Lengkapi setiap domain dan port yang dipetakan sebelum menyimpan",
+ "domainsTitle": "Domain",
+ "routingUpdated": "Routing domain diperbarui",
+ "routingUpdateFailed": "Gagal memperbarui routing domain",
+ "primaryUpdated": "Domain utama diperbarui",
+ "setPrimaryFailed": "Gagal menyetel domain utama",
+ "routeUpdateFailed": "Gagal memperbarui route layanan",
+ "publishing": "Mempublikasikan — mendeploy untuk menginstal edge dan menerapkan domain Anda…",
+ "publishFailed": "Domain disimpan, tetapi deploy tidak dimulai — deploy ulang untuk mempublikasikannya.",
+ "enterPortShort": "Masukkan port yang harus dirutekan domain ini.",
+ "noServicePort": "Tidak ada layanan yang mempublikasikan port {port}. Tambah port itu ke layanan terlebih dahulu.",
+ "enterCustom": "Masukkan domain kustom.",
+ "enterSubdomain": "Masukkan subdomain."
+ },
+ "edge": {
+ "setUp": "Atur edge",
+ "ready": "Edge siap",
+ "checking": "Memeriksa edge…",
+ "recheck": "Periksa ulang",
+ "foreignProxyHint": "Proxy lain memegang 80/443"
+ },
+ "diagnosis": {
+ "title": "Mengapa ini tidak berfungsi",
+ "noneYet": "Belum ada pemeriksaan yang dijalankan, jadi belum ada hasil yang ditampilkan. Tambah record DNS yang ditampilkan untuk domain ini, lalu jalankan Verifikasi.",
+ "attempts": "{count} percobaan gagal sejauh ini.",
+ "pillHint": "Tampilkan alasannya"
+ }
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/id/projects.json b/apps/dashboard/src/i18n/locales/id/projects.json
new file mode 100644
index 000000000..6d881a177
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/id/projects.json
@@ -0,0 +1,518 @@
+{
+ "list": {
+ "loading": "Memuat...",
+ "countOne": "{count} proyek",
+ "countOther": "{count} proyek",
+ "noTargetProjects": "Belum ada proyek yang di-deploy ke target ini.",
+ "noTargetDesc": "Deploy aplikasi ke target ini, atau buat proyek baru untuk memulai."
+ },
+ "status": {
+ "live": "Aktif",
+ "paused": "Dijeda",
+ "attention": "Tindakan Diperlukan",
+ "queued": "Antrean",
+ "building": "Membangun",
+ "deploying": "Mendeploy",
+ "migrating": "Migrasi",
+ "failed": "Gagal",
+ "cancelled": "Dibatalkan",
+ "deleting": "Menghapus",
+ "draft": "Draf"
+ },
+ "serverCta": {
+ "title": "Deploy ke server Anda sendiri",
+ "description": "Hubungkan server melalui SSH untuk menjalankan proyek di infrastruktur Anda sendiri bersama Openship Cloud.",
+ "button": "Hubungkan server"
+ },
+ "filters": {
+ "title": "Filter berdasarkan target",
+ "subtitle": "Di mana proyek di-deploy",
+ "allProjects": "Semua proyek",
+ "cloud": "Openship Cloud",
+ "local": "Lokal"
+ },
+ "card": {
+ "liveVersion": "Versi aktif v{version}",
+ "appBadge": "Aplikasi",
+ "sourceLocal": "Lokal",
+ "services": "Layanan",
+ "static": "Statis",
+ "standalone": "Standalone",
+ "worker": "Worker",
+ "updateAvailable": "Pembaruan"
+ },
+ "hosting": {
+ "cloud": "Cloud",
+ "server": "Server",
+ "local": "Lokal"
+ },
+ "time": {
+ "justNow": "baru saja",
+ "minutesAgo": "{count}m lalu",
+ "hoursAgo": "{count}j lalu",
+ "daysAgo": "{count}h lalu",
+ "monthsAgo": "{count}bl lalu"
+ },
+ "deploymentCard": {
+ "visit": "Kunjungi"
+ },
+ "env": {
+ "productionFallback": "Produksi",
+ "switchAria": "Beralih environment",
+ "newEnvironment": "Environment baru",
+ "backToEnvironments": "Kembali ke environment",
+ "searchBranches": "Cari branch",
+ "refreshBranches": "Segarkan branch",
+ "noBranches": "Tidak ada branch ditemukan",
+ "environmentName": "Nama environment",
+ "branchLabel": "Label branch",
+ "cancel": "Batal",
+ "create": "Buat",
+ "manualEnvironment": "Environment manual",
+ "toastBranchesTitle": "Branch",
+ "toastEnvironmentTitle": "Environment",
+ "failedLoadBranches": "Gagal memuat branch",
+ "failedCreateEnvironment": "Gagal membuat environment"
+ },
+ "detail": {
+ "breadcrumbDashboard": "Dashboard",
+ "projectFallback": "Proyek",
+ "overviewFallback": "Ringkasan"
+ },
+ "help": {
+ "contactSupport": "Hubungi Dukungan",
+ "reportIssue": "Laporkan Masalah",
+ "sendFeedback": "Kirim Masukan",
+ "documentation": "Dokumentasi",
+ "joinCommunity": "Gabung Komunitas"
+ },
+ "delete": {
+ "successProject": "Proyek berhasil dihapus",
+ "successEnvironment": "Environment berhasil dihapus",
+ "orphanCleanup": "Proyek dihapus. {count} sumber daya remote akan dibersihkan saat server dapat dijangkau.",
+ "orphanCleanupTitle": "Dihapus (pembersihan tertunda)",
+ "partialCleanup": "Proyek dihapus, tetapi {count} langkah pembersihan gagal. Ops perlu memverifikasi.",
+ "partialCleanupTitle": "Pembersihan parsial",
+ "failed": "Gagal menghapus proyek",
+ "cancellingActiveWork": "Membatalkan pekerjaan aktif, lalu menghapus…",
+ "cleaningUpTitle": "Membersihkan",
+ "hasActiveWork": "Proyek memiliki pekerjaan aktif — coba lagi sebentar lagi",
+ "cannotDeleteTitle": "Tidak dapat menghapus",
+ "deletionInProgress": "Penghapusan lain sudah berjalan untuk proyek ini",
+ "deletionInProgressTitle": "Penghapusan sedang berlangsung",
+ "confirmForceOrphan": "Beberapa sumber daya tidak dapat dihapus dari server. Hapus proyek tetapi bersihkan secara otomatis nanti?",
+ "teardownFailedAt": "Pembongkaran gagal di: {reasons}. Coba lagi, atau paksa hapus untuk membiarkannya.",
+ "teardownFailed": "Pembongkaran gagal",
+ "cleanupFailedTitle": "Pembersihan gagal",
+ "alreadyDeleted": "Proyek sudah dihapus",
+ "forceModalTitle": "Tidak dapat menghapusnya dari sumber",
+ "forceModalBody": "Runtime tidak dapat dijangkau (atau terus gagal), jadi proyek ini tidak dibongkar. Anda dapat menghapusnya dari Openship tetapi — catatan dihapus sekarang dan sumber daya yang tersisa dipulihkan secara otomatis setelah server dapat dijangkau.",
+ "forceModalConfirm": "Hapus dari penyimpanan",
+ "forceModalCancel": "Batal",
+ "unlinked": "Dihubungkan dari {projects} — deploy ulang untuk menerapkan.",
+ "unlinkedTitle": "Proyek yang terhubung diperbarui"
+ },
+ "connections": {
+ "useInProject": "Gunakan di proyek",
+ "title": "Gunakan di proyek",
+ "subtitle": "Hubungkan database ini ke proyek lain sebagai koneksi siap pakai.",
+ "targetLabel": "Proyek",
+ "targetPlaceholder": "Pilih proyek…",
+ "valueLabel": "Nilai untuk disuntikkan",
+ "valuesLabel": "Nilai untuk disuntikkan",
+ "connectedN": "Menghubungkan {count} nilai ke {project} — deploy ulang untuk menerapkan.",
+ "modeInternal": "Internal (direkomendasikan)",
+ "modeInternalDesc": "Jangkau secara pribadi melalui jaringan bersama — tanpa port publik. Server self-hosted yang sama.",
+ "modePublic": "Publik",
+ "modePublicDesc": "Gunakan host:port yang dipublikasikan. Berfungsi di mana saja; port database harus terbuka.",
+ "envKeyLabel": "Variabel environment",
+ "redeployHint": "Diterapkan pada deploy berikutnya dari target.",
+ "cancel": "Batal",
+ "connect": "Hubungkan",
+ "connected": "Terhubung — deploy ulang target untuk menerapkan.",
+ "failed": "Tidak dapat menghubungkan layanan.",
+ "cardTitle": "Layanan yang terhubung",
+ "removed": "Koneksi dihapus — deploy ulang untuk menerapkan.",
+ "remove": "Hapus",
+ "modeInternalShort": "Internal",
+ "modePublicShort": "Publik",
+ "guideIntroFallback": "Proyek Anda mendapatkan koneksi ini, siap digunakan.",
+ "whatYouGet": "Apa yang didapatkan aplikasi Anda",
+ "advancedValues": "Lanjutan",
+ "copy": "Salin",
+ "copied": "Disalin",
+ "reveal": "Tampilkan",
+ "hide": "Sembunyikan",
+ "openLocalhost": "Buka di localhost",
+ "forwardLocalhost": "Teruskan ke localhost",
+ "openShort": "Buka",
+ "openUrl": "Buka di tab baru",
+ "forwardShort": "Teruskan",
+ "usedByTitle": "Digunakan oleh",
+ "usedByHint": "{count} proyek bergantung pada koneksi aplikasi ini. Putuskan tautan dari proyek yang menggunakannya.",
+ "usedByOpen": "Buka proyek",
+ "linkedCountOne": "{count} aplikasi terhubung",
+ "linkedCountOther": "{count} aplikasi terhubung",
+ "linkedHint": "Dijalankan terhadap, bukan dijalankan oleh proyek ini.",
+ "linkedBadge": "Terhubung",
+ "unlink": "Putuskan",
+ "openApp": "Buka aplikasi",
+ "internalTitle": "Alamat internal",
+ "internalDescription": "Jangkau proyek ini secara pribadi dari layanan lain di server yang sama — tanpa port publik."
+ },
+ "sidebar": {
+ "project": "Proyek",
+ "untitledProject": "Proyek Tanpa Judul",
+ "openAria": "Buka {name}",
+ "openProjectFallback": "proyek",
+ "open": "Buka",
+ "local": "Lokal",
+ "production": "Produksi",
+ "lastDeploy": "Deploy Terakhir",
+ "noDomain": "Tanpa domain",
+ "addDomain": "Tambah domain",
+ "tabs": {
+ "appSettings": "Pengaturan",
+ "overview": "Ringkasan",
+ "services": "Aplikasi & Layanan",
+ "domains": "Domain & Route",
+ "deployments": "Deployment",
+ "health": "Kesehatan",
+ "monitoring": "Pemantauan",
+ "source": "Sumber",
+ "webhooks": "Webhook",
+ "runtime": "Konfigurasi",
+ "logs": "Log",
+ "backup": "Backup",
+ "advanced": "Lanjutan"
+ }
+ },
+ "notFound": {
+ "title": "Proyek Tidak Ditemukan",
+ "subtitle": "Kami tidak dapat menemukan proyek ini",
+ "bodyPrefix": "Proyek",
+ "bodySuffix": "tidak ada di workspace Anda.",
+ "reasonDeleted": "Proyek mungkin telah dihapus",
+ "reasonAccess": "Anda mungkin tidak memiliki akses ke proyek ini",
+ "reasonUrl": "URL domain mungkin salah",
+ "dashboard": "Dashboard",
+ "reload": "Muat Ulang",
+ "needHelp": "Butuh bantuan?",
+ "documentation": "Dokumentasi",
+ "support": "Dukungan"
+ },
+ "draft": {
+ "headingFailed": "Deploy terakhir tidak selesai",
+ "headingCancelled": "Deploy dibatalkan",
+ "headingReady": "Siap untuk di-deploy",
+ "subtextDraft": "Proyek ini belum pernah di-deploy. Deploy untuk mendapatkan URL aktif, log, dan analitik.",
+ "subtextOther": "Proyek ini belum memiliki deployment aktif. Tinjau sumber dan coba lagi.",
+ "deployNow": "Deploy sekarang",
+ "connectSource": "Hubungkan sumber",
+ "settings": "Pengaturan deployment",
+ "detailsTitle": "Detail",
+ "detailsDescription": "Apa yang di-deploy proyek ini dan ke mana",
+ "type": "Jenis",
+ "typeApp": "Aplikasi",
+ "typeProject": "Proyek",
+ "target": "Target",
+ "targetPending": "Dipilih saat deploy",
+ "services": "Layanan",
+ "created": "Dibuat",
+ "attemptsTitle": "Percobaan deploy",
+ "attemptsDescription": "Setiap build untuk proyek ini — klik satu untuk membukanya",
+ "sourceTitle": "Sumber",
+ "sourceDescription": "Dari mana proyek ini di-deploy",
+ "repository": "Repository",
+ "branch": "Branch",
+ "localPath": "Path lokal",
+ "framework": "Framework",
+ "build": "Build",
+ "noSourceText": "Belum ada sumber yang terhubung.",
+ "connectLink": "Hubungkan repository atau path lokal",
+ "managedImages": "Image container yang dikelola",
+ "managedImagesText": "Aplikasi ini berjalan dari image yang sudah dibangun — tidak ada repository untuk dihubungkan. Cukup deploy.",
+ "deleteTitle": "Hapus proyek",
+ "deleteDescription": "Hapus proyek ini — tidak dapat dibatalkan",
+ "deleteConfirmPrefix": "Hapus secara permanen",
+ "deleteConfirmSuffix": "?",
+ "cancel": "Batal",
+ "delete": "Hapus"
+ },
+ "overview": {
+ "platformCloud": "Openship Cloud",
+ "platformServer": "Self-hosted (Server)",
+ "platformLocal": "Self-hosted (Lokal)",
+ "modeStatic": "Situs Statis",
+ "modeStandalone": "Standalone",
+ "modeServer": "Server",
+ "modeWorker": "Worker",
+ "infrastructure": "Infrastruktur",
+ "platform": "Platform",
+ "mode": "Mode",
+ "port": "Port",
+ "sourceCicd": "Sumber & CI/CD",
+ "repository": "Repository",
+ "notConnected": "Tidak terhubung",
+ "branch": "Branch",
+ "autoDeploy": "Auto Deploy",
+ "webhook": "Webhook",
+ "traffic": "Lalu Lintas",
+ "noTrafficData": "Belum ada data lalu lintas",
+ "services": "Layanan",
+ "noServicesConnected": "Tidak ada layanan yang terhubung",
+ "manage": "Kelola",
+ "topPaths": "Path Teratas",
+ "requestCount": "{count} req",
+ "statusActive": "Aktif",
+ "statusOff": "Mati",
+ "server": "Server"
+ },
+ "stats": {
+ "serverRequests": "Permintaan Server",
+ "uniqueIPs": "IP Unik",
+ "avgResponse": "Rata-rata Respons",
+ "bandwidthOut": "Bandwidth Keluar",
+ "bandwidth": "Bandwidth",
+ "requestsSubtext": "{total} total, {avg}/jam rata-rata",
+ "uniqueIPsSubtext": "{pct}% dari total",
+ "responseTime": "Waktu respons",
+ "bandwidthInSubtext": "{value} masuk",
+ "loading": "Memuat...",
+ "noTraffic": "Belum ada lalu lintas tercatat",
+ "noVisitors": "Belum ada pengunjung",
+ "waitingRequests": "Menunggu permintaan",
+ "noTransfer": "Belum ada transfer"
+ },
+ "monitoring": {
+ "noDataTitle": "Belum ada data pemantauan",
+ "noDataDescription": "Analitik akan muncul di sini setelah aplikasi yang di-deploy mulai menerima lalu lintas.",
+ "resourcesTitle": "Penggunaan sumber daya",
+ "resourcesSubtitle": "Langsung, disegarkan setiap beberapa detik",
+ "cpu": "CPU",
+ "memory": "Memori",
+ "network": "Jaringan",
+ "diskIo": "Disk I/O",
+ "ofCores": "dari {cores} core",
+ "ofMemory": "dari {total}",
+ "servicesTitle": "Layanan",
+ "scrollServicesLeft": "Gulir layanan ke kiri",
+ "scrollServicesRight": "Gulir layanan ke kanan",
+ "unsupportedTitle": "Penggunaan sumber daya tidak tersedia",
+ "reconnect": "Hubungkan Ulang",
+ "live": "Langsung",
+ "offline": "Terputus",
+ "visitorsTitle": "Pengunjung menurut negara",
+ "visitorsSubtitle": "{total} permintaan dari {countries} negara",
+ "visitors": "Pengunjung",
+ "approximate": "perkiraan",
+ "geoUnavailableTitle": "Data negara tidak tersedia di edge ini",
+ "geoUnavailableBody": "Proxy edge tidak dapat menyelesaikan negara pengunjung, jadi peta tetap kosong. Total lalu lintas tidak terpengaruh.",
+ "noCountries": "Belum ada data negara untuk periode ini.",
+ "statusesTitle": "Respons",
+ "share": "{pct}%",
+ "visitorDays": "Hari-pengunjung",
+ "peakDayTooltip": "Hari tersibuk: {peak}. Pengunjung yang kembali dihitung sekali per hari, jadi totalnya adalah batas atas.",
+ "historyTitle": "Penggunaan dari waktu ke waktu",
+ "historyEmptyTitle": "Belum ada riwayat penggunaan",
+ "historyEmpty": "CPU dan memori diambil sampel setiap beberapa menit — titik pertama segera muncul di sini setelah aplikasi Anda mulai berjalan.",
+ "historyWaiting": "Menunggu sampel pertama",
+ "allServices": "Semua layanan",
+ "requests": "permintaan",
+ "moreCountries": "+{count} negara lagi",
+ "previewSample": "Pratinjau dengan data sampel",
+ "previewActive": "Data sampel — bukan nyata",
+ "previewExit": "Keluar pratinjau",
+ "colorTheme": "Tema",
+ "colorFlag": "Bendera",
+ "tooltipRequests": "{count} permintaan · {pct}%",
+ "allDomains": "Semua domain",
+ "domainScope": "Cakupan ke domain",
+ "liveHits": "Langsung",
+ "liveHitsHint": "Animasikan setiap permintaan masuk di peta, dari stream yang sama yang ditampilkan tab Log",
+ "liveHitsWaiting": "Menunggu permintaan…",
+ "statusesUnclassified": "{count} tanpa kelas status",
+ "trafficTitle": "Ringkasan lalu lintas",
+ "trafficSummary": "{count} permintaan",
+ "servicesMore": "+{count} lagi",
+ "servicesLess": "Tampilkan lebih sedikit",
+ "pathsOff": {
+ "title": "Top Paths dinonaktifkan",
+ "desc": "Penghitungan per-path adalah satu metrik yang tidak gratis — sekitar {share} dari pekerjaan yang dilakukan edge pada setiap permintaan. Yang lainnya di tab ini dikumpulkan tanpa biaya.",
+ "cta": "Aktifkan Top Paths",
+ "enabling": "Mengaktifkan…",
+ "disable": "Nonaktifkan Top Paths"
+ },
+ "liveFeed": "Hit terbaru",
+ "liveFeedHint": "Tampilkan daftar hit terbaru di peta (tab Log memiliki detail lengkap)"
+ },
+ "services": {
+ "failedLoad": "Gagal memuat layanan",
+ "retry": "Coba Lagi",
+ "refresh": "Segarkan",
+ "addService": "Tambah",
+ "allServices": "Semua",
+ "countOne": "{count} Aplikasi / Layanan",
+ "countOther": "{count} Aplikasi & Layanan",
+ "chooseService": "Pilih satu untuk membuka layar pengaturan lengkapnya.",
+ "emptyTitle": "Belum ada aplikasi atau layanan",
+ "emptyDescription": "Tambahkan database, cache, atau image container apa pun untuk dijalankan bersama proyek Anda. Pilih dari katalog atau tempel image Docker apa pun.",
+ "driftCountOne": "{count} layanan berubah di repo",
+ "driftCountOther": "{count} layanan berubah di repo",
+ "driftDescription": "docker-compose repo mengubah nilai yang telah Anda edit di sini. Edit Anda masih aktif — untuk masing-masing, pertahankan milik Anda atau ambil milik repo.",
+ "keepMine": "Pertahankan milik saya",
+ "acceptUpstream": "Terima upstream",
+ "public": "Publik",
+ "internal": "Internal",
+ "upstreamChange": "Perubahan upstream",
+ "port": "port {port}",
+ "toastServiceTitle": "Layanan",
+ "toastAddedDeploying": "{name} ditambahkan - mendeploy…",
+ "toastStarting": "{name} sedang dimulai",
+ "toastSavedDeploy": "{name} disimpan - deploy proyek untuk memulainya",
+ "toastDeployFailed": "Deploy gagal",
+ "toastAppliedRepo": "{name}: perubahan repo diterapkan",
+ "toastKeptEdits": "{name}: edit Anda dipertahankan",
+ "failedResolveDrift": "Gagal menyelesaikan drift",
+ "failedCreateService": "Gagal membuat layanan",
+ "requestFailed": "Permintaan gagal"
+ },
+ "serviceStatus": {
+ "running": "Berjalan",
+ "stopped": "Berhenti",
+ "disabled": "Dinonaktifkan",
+ "failed": "Gagal",
+ "starting": "Memulai",
+ "restarting": "Memulai Ulang",
+ "unknown": "Tidak Diketahui"
+ },
+ "routingRetry": {
+ "title": "Routing domain memerlukan perhatian",
+ "description": "Aplikasi aktif di server, tetapi route di depannya tidak bersinkronisasi. Coba lagi perbaikan, atau periksa DNS dan edge di server ini.",
+ "retry": "Coba lagi routing",
+ "retrying": "Mencoba ulang…",
+ "success": "Routing domain bersinkronisasi.",
+ "failed": "Tidak dapat bersinkronisasi routing domain."
+ },
+ "deployBlocked": {
+ "redeploy": "Deploy Ulang"
+ },
+ "redeploy": {
+ "deployStartedLong": "Deploy dimulai — membutuhkan waktu lebih lama dari biasanya, membuka deployment.",
+ "deployingTitle": "Mendeploy",
+ "couldNotRefresh": "Tidak dapat menyegarkan deployment. Periksa log deployment untuk detail.",
+ "couldNotRedeploy": "Tidak dapat memulai redeployment. Periksa log deployment untuk detail.",
+ "errorTitle": "Kesalahan",
+ "failedRedeploy": "Gagal memulai redeployment",
+ "actionRequiredTitle": "Tindakan diperlukan — beberapa layanan gagal",
+ "actionRequiredDescription": "Rilis terbaru proyek ini di-deploy dengan layanan yang gagal dan menunggu Anda untuk menerima atau menolaknya.",
+ "reviewDeployment": "Tinjau deployment",
+ "newCommitTitle": "Commit baru tersedia",
+ "newCommitOn": "pada",
+ "newCommitDeployedOn": "— Anda di-deploy pada",
+ "newVersionTitle": "Versi baru tersedia",
+ "newVersionAvailable": "Rilis lebih baru tersedia:",
+ "newVersionDeployed": "— Anda di-deploy pada",
+ "deployVersion": "Deploy versi baru",
+ "deploying": "Mendeploy…",
+ "redeployLatest": "Deploy Ulang Terbaru",
+ "deployLatestTitle": "Deploy Perubahan Terbaru",
+ "deployLatestMulti": "Mengambil commit terbaru dan membangun ulang hanya layanan yang file-nya berubah sejak deploy terakhir.",
+ "deployLatestSingle": "Mengambil commit terbaru dan deploy ulang menggunakan konfigurasi saat ini.",
+ "deployingButton": "Mendeploy...",
+ "redeployProject": "Deploy Ulang Proyek",
+ "refreshEnv": "Segarkan env",
+ "rebuildAll": "Bangun Ulang Semua",
+ "noPublicDomainTitle": "Tidak ada domain publik yang terhubung",
+ "noPublicDomainDescOne": "Proyek ini memiliki {count} layanan dengan port terekspos, tetapi tidak ada yang dikonfigurasi dengan domain yang dapat dijangkau. Jika Anda deploy sekarang, stack dapat berjalan internal, tetapi pengguna tidak akan dapat mengaksesnya dari URL publik.",
+ "noPublicDomainDescOther": "Proyek ini memiliki {count} layanan dengan port terekspos, tetapi tidak ada yang dikonfigurasi dengan domain yang dapat dijangkau. Jika Anda deploy sekarang, stack dapat berjalan internal, tetapi pengguna tidak akan dapat mengaksesnya dari URL publik.",
+ "openServices": "Buka Layanan",
+ "deployAnyway": "Deploy saja",
+ "suggestedFix": "Perbaikan yang disarankan",
+ "fixStep1": "Buka tab Layanan.",
+ "fixStep2": "Pilih layanan yang harus publik.",
+ "fixStep3": "Aktifkan eksposur domain dan pilih port publik."
+ },
+ "incomingWebhooks": {
+ "title": "Webhook masuk",
+ "subtitle": "Picu deploy atau job dari URL eksternal. Setiap webhook mendapatkan alamat dan autentikasinya sendiri.",
+ "create": "Webhook baru",
+ "loading": "Memuat webhook…",
+ "empty": "Belum ada webhook masuk. Buat satu untuk memicu deploy atau job dari URL eksternal.",
+ "deleteConfirm": "Hapus webhook \"{name}\"? URL-nya akan berhenti berfungsi segera.",
+ "removed": "Webhook dihapus.",
+ "rotated": "Secret diputar — perbarui pemanggil dengan nilai baru.",
+ "created": "Webhook dibuat.",
+ "failed": "Terjadi kesalahan.",
+ "toggleAria": "Aktifkan atau nonaktifkan webhook ini",
+ "rotate": "Putar secret",
+ "delete": "Hapus",
+ "urlLabel": "URL Pengiriman",
+ "secretLabel": "Secret",
+ "copy": "Salin",
+ "copied": "Disalin",
+ "reveal": "Tampilkan",
+ "hide": "Sembunyikan",
+ "authBadge": {
+ "token": "Token",
+ "hmac": "HMAC",
+ "none": "Terbuka"
+ },
+ "summaryDeploy": "Deploy ulang proyek ini",
+ "summaryDeployService": "Deploy ulang layanan {service}",
+ "summaryJob": "Menjalankan job {job}",
+ "createTitle": "Webhook masuk baru",
+ "createSubtitle": "Pilih apa yang dilakukannya dan bagaimana pemanggil mengotentikasi.",
+ "nameLabel": "Nama",
+ "namePlaceholder": "misalnya CI deploy hook",
+ "actionLabel": "Aksi",
+ "actionDeploy": "Deploy",
+ "actionDeployDesc": "Deploy ulang proyek ini (atau satu layanan) saat dipanggil.",
+ "actionJob": "Jalankan job",
+ "actionJobDesc": "Jalankan salah satu job Anda saat dipanggil.",
+ "serviceLabel": "Layanan (opsional)",
+ "serviceAll": "Seluruh proyek",
+ "jobLabel": "Job",
+ "jobPlaceholder": "Pilih job…",
+ "authLabel": "Autentikasi",
+ "authToken": "Token",
+ "authTokenDesc": "Token bearer secret — kirim sebagai Authorization: Bearer, atau ?token=.",
+ "authHmac": "Tanda tangan HMAC",
+ "authHmacDesc": "Pemanggil menandatangani body dengan secret bersama (X-Signature-256).",
+ "authNone": "Terbuka",
+ "authNoneDesc": "Tanpa autentikasi — siapa pun yang memiliki URL dapat memicunya. Gunakan dengan hati-hati.",
+ "cancel": "Batal"
+ },
+ "health": {
+ "title": "Kesehatan",
+ "subtitle": "Kegagalan container yang tercatat untuk proyek ini oleh pemantau kesehatan.",
+ "refresh": "Segarkan",
+ "openTitle": "Insiden terbuka",
+ "recentTitle": "Terbaru ({days} hari)",
+ "emptyOpen": "Tidak ada insiden terbuka.",
+ "emptyRecent": "Tidak ada insiden tercatat.",
+ "emptyUnwatched": "Tidak ada yang dicatat — pemantau kesehatan mati.",
+ "watchOffTitle": "Pemantau kesehatan container mati",
+ "watchOffBody": "Tidak ada insiden yang dicatat dan tidak ada alert yang dikirim, jadi daftar kosong di sini tidak berarti semuanya sehat.",
+ "watchOffCta": "Nyalakan di Job",
+ "serverDownTitle": "Openship tidak dapat menjangkau server proyek ini",
+ "serverDownBody": "Tidak ada di mesin ini yang diamati selama {duration}, jadi insiden di bawah mungkin sudah usang.",
+ "loadFailed": "Tidak dapat memuat insiden.",
+ "ongoing": "berlangsung selama {duration}",
+ "lasted": "berlangsung {duration}",
+ "started": "dimulai {when}",
+ "recovered": "Pulih",
+ "exitCode": "keluar {code}",
+ "restarts": "{count} restart",
+ "oomKilled": "kehabisan memori",
+ "showLog": "Tampilkan cuplikan log",
+ "hideLog": "Sembunyikan cuplikan log",
+ "kinds": {
+ "down": "Mati",
+ "crash_loop": "Siklus crash",
+ "unhealthy": "Tidak sehat",
+ "server_unreachable": "Server tidak terjangkau"
+ }
+ },
+ "migrationAwaitingHint": "Migrasi menunggu Anda — selesaikan pemotongan atau batalkan di tab Lanjutan proyek"
+}
diff --git a/apps/dashboard/src/i18n/locales/id/servers.json b/apps/dashboard/src/i18n/locales/id/servers.json
new file mode 100644
index 000000000..394da46e3
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/id/servers.json
@@ -0,0 +1,565 @@
+{
+ "github": {
+ "title": "Akses GitHub",
+ "desc": "Cara server ini mengautentikasi ke GitHub untuk meng-clone repo privat.",
+ "loadFailed": "Tidak dapat memuat status GitHub",
+ "connected": "Terhubung",
+ "modeToken": "Token",
+ "modeSshKey": "Kunci SSH",
+ "modeDeployKey": "Deploy key",
+ "connectDevice": "Hubungkan dengan GitHub",
+ "deviceHint": "Buka tautan dan masukkan kode ini:",
+ "open": "Buka GitHub",
+ "connecting": "Menunggu otorisasi…",
+ "connectFailed": "Koneksi gagal",
+ "connectedToast": "GitHub terhubung untuk server ini",
+ "patPlaceholder": "ghp_… (tempel token)",
+ "patHint": "Personal access token yang di-encrypt dan disimpan untuk server ini.",
+ "patLabel": "Personal access token",
+ "or": "atau",
+ "save": "Simpan",
+ "savedToast": "Tersimpan",
+ "saveFailed": "Gagal menyimpan",
+ "tokenAs": "Diautentikasi sebagai",
+ "sshGenerate": "Buat kunci SSH",
+ "sshAddHint": "Tambahkan kunci ini ke akun GitHub Anda, lalu deploy:",
+ "openGithubKeys": "Buka kunci SSH GitHub",
+ "copy": "Salin",
+ "deployKeyUse": "Gunakan deploy key per-repo",
+ "deployKeyHint": "Openship mendaftarkan deploy key baca-saja di setiap repo saat pertama kali deploy di sana.",
+ "deployKeyActive": "Mode deploy-key aktif — kunci dibuat saat deploy pertama.",
+ "deployKeysRegistered": "deploy key terdaftar",
+ "disconnect": "Putuskan",
+ "disconnectedToast": "GitHub terputus"
+ },
+ "list": {
+ "title": "Server",
+ "subtitle": "Kelola server deployment dan infrastruktur Anda",
+ "addServer": "Tambah Server",
+ "addRemoteServer": "Tambah server remote",
+ "addThisMachine": "Tambah mesin ini",
+ "addThisMachineError": "Tidak dapat menambahkan mesin ini sebagai server.",
+ "emptyTitle": "Belum ada server",
+ "emptyDescription": "Hubungkan server via SSH dan Openship akan menangani sisanya - Docker, edge, SSL, dan deployment semuanya diatur secara otomatis.",
+ "addFirstServer": "Tambah Server Pertama Anda",
+ "whatGetsConfigured": "Apa yang dikonfigurasi",
+ "featContainer": "Container runtime",
+ "featContainerDesc": "Menjalankan setiap aplikasi secara terisolasi",
+ "featProxy": "Reverse proxy",
+ "featProxyDesc": "Routing & SSL",
+ "monitoring": "Monitoring",
+ "monitoringDesc": "Health check",
+ "gitDesc": "Deployment sumber",
+ "connected": "Terhubung",
+ "error": "Kesalahan",
+ "checking": "Memeriksa",
+ "online": "Online",
+ "offline": "Offline",
+ "authKey": "kunci",
+ "authPassword": "kata sandi",
+ "forwarding": "{n} penerusan",
+ "quickInfo": "Info Cepat",
+ "serverOverview": "Ringkasan server",
+ "totalServers": "Total Server",
+ "connectedLabel": "Terhubung",
+ "errors": "Kesalahan",
+ "seeDocs": "Lihat dokumentasi",
+ "projects": "Proyek",
+ "regions": "Region",
+ "thisServer": "Server Ini",
+ "currentHost": "Host saat ini",
+ "projectCountOne": "{n} proyek",
+ "projectCountMany": "{n} proyek",
+ "infra": {
+ "title": "Komponen platform",
+ "subtitle": "Edge & email di seluruh fleet Anda",
+ "attentionOne": "1 perlu diperhatikan",
+ "attentionMany": "{n} perlu diperhatikan",
+ "updates": "{n} dengan pembaruan",
+ "healthy": "{n} sehat",
+ "allHealthy": "Semua komponen sudah terbaru",
+ "updateAll": "Perbarui semua ({n})",
+ "restartStopped": "Restart yang berhenti ({n})",
+ "scan": "Periksa ulang setiap server",
+ "openServerHint": "Buka server untuk menyelesaikan ini — mereka membutuhkan pengaturan atau persetujuan Anda.",
+ "filterAll": "Semua",
+ "filterAttention": "Perlu diperhatikan",
+ "filterUpdates": "Pembaruan",
+ "filterHealthy": "Sehat",
+ "searchPlaceholder": "Cari server…",
+ "noMatches": "Tidak ada server yang cocok dengan filter ini.",
+ "chipEdgeDown": "edge mati",
+ "chipMailDown": "email mati",
+ "chipEdgeMissing": "tidak ada edge",
+ "chipUpdateOne": "1 pembaruan",
+ "chipUpdates": "{n} pembaruan",
+ "chipUpdating": "memperbarui",
+ "applyingOne": "Memperbarui 1 komponen",
+ "applyingMany": "Memperbarui {n} komponen",
+ "restartingOne": "Merestart 1 komponen",
+ "restartingMany": "Merestart {n} komponen",
+ "stateQueued": "Antrian",
+ "stepPull": "Menarik image",
+ "stepRecreate": "Membuat ulang",
+ "stepVerify": "Memverifikasi",
+ "moreTargets": "+{n} lagi",
+ "viewLogs": "Lihat log",
+ "doneOne": "1 komponen diperbarui",
+ "doneMany": "{n} komponen diperbarui",
+ "failedOne": "1 komponen tidak selesai",
+ "failedMany": "{n} komponen tidak selesai",
+ "skippedOne": "1 perlu diperhatikan di halaman sendiri",
+ "skippedMany": "{n} perlu diperhatikan di halaman masing-masing",
+ "nothingToDo": "Tidak ada yang perlu diterapkan",
+ "applyFailed": "Tidak dapat memulai pembaruan"
+ }
+ },
+ "containers": {
+ "subtitle": "Container edge dan email di server ini",
+ "updatesAvailableOne": "1 pembaruan tersedia",
+ "updatesAvailableMany": "{n} pembaruan tersedia",
+ "scan": "Pindai",
+ "scanning": "Memindai…",
+ "componentEdge": "Edge",
+ "componentMail": "Mesin email",
+ "down": "mati",
+ "goneEdge": "Container hilang — jalankan ulang setup untuk membuatnya kembali.",
+ "goneMail": "Container hilang — jalankan ulang setup email untuk membuatnya kembali.",
+ "legacyEngine": "Instalasi lama · tidak tercontainerisasi",
+ "update": "Perbarui",
+ "updating": "Memperbarui…",
+ "fix": "Perbaiki",
+ "starting": "Memulai…",
+ "stopped": "Berhenti",
+ "running": "Berjalan",
+ "upToDate": "Sudah terbaru",
+ "notInstalled": "Belum diinstal",
+ "installEdge": "Instal edge",
+ "edgeAbsentImpactOne": "{n} proyek di-deploy di sini tanpa yang merutekan lalu lintas ke sana.",
+ "edgeAbsentImpactMany": "{n} proyek di-deploy di sini tanpa yang merutekan lalu lintas ke sana."
+ },
+ "modules": {
+ "title": "Pembaruan modul tersedia",
+ "checkFailedTitle": "Pemeriksaan pembaruan modul gagal",
+ "needsConfirmation": "membutuhkan konfirmasi",
+ "update": "Perbarui",
+ "updating": "Memperbarui…",
+ "confirmTitle": "Perbarui {module}?",
+ "confirmIntro": "Pembaruan ini mencakup perubahan yang membutuhkan persetujuan Anda:",
+ "cancel": "Batal",
+ "updateFailed": "Pembaruan {module} gagal"
+ },
+ "tabsNav": {
+ "servers": "Server",
+ "cluster": "Kluster",
+ "networking": "Jaringan"
+ },
+ "comingSoon": {
+ "badge": "Segera hadir",
+ "clusterTitle": "Kluster",
+ "clusterBody": "Kelompokkan server ke dalam kluster dan biarkan Openship menjadwalkan dan menyeimbangkan deployment di antara mereka.",
+ "networkingTitle": "Jaringan",
+ "networkingBody": "Tautan privat, routing mesh, dan aturan firewall di seluruh server Anda — dikelola di satu tempat."
+ },
+ "form": {
+ "sshConnection": "Koneksi SSH",
+ "sshConnectionDesc": "Hubungkan ke server Anda via SSH",
+ "serverName": "Nama Server",
+ "serverNamePlaceholder": "misal Produksi, Staging, Dev...",
+ "serverNameHelp": "Label opsional yang ditampilkan di daftar server dan header.",
+ "serverIp": "Alamat IP Server",
+ "port": "Port",
+ "portPlaceholder": "misal 22",
+ "username": "Nama Pengguna",
+ "usernamePlaceholder": "misal root",
+ "authentication": "Autentikasi",
+ "password": "Kata Sandi",
+ "sshKey": "Kunci SSH",
+ "agent": "Agent",
+ "passwordPlaceholderKeep": "Biarkan kosong untuk mempertahankan kata sandi saat ini",
+ "passwordPlaceholderEnter": "Masukkan kata sandi server",
+ "agentInfoBefore": "Menghubungkan menggunakan SSH agent mesin ini — seperti VSCode, tidak perlu kata sandi atau kunci. Host yang menjalankan Openship harus sudah memiliki akses ke server ini (kunci dimuat di ",
+ "agentInfoAfter": ").",
+ "keyModePaste": "Tempel / Unggah",
+ "keyModePath": "Jalur host",
+ "keyPath": "Jalur Kunci",
+ "keyPathBrowse": "Jelajahi…",
+ "keyPathHelp": "Jalur absolut ke kunci privat di mesin yang menjalankan Openship.",
+ "keyPaste": "Kunci Privat",
+ "keyPastePlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----\n…",
+ "keyPasteHelp": "Kunci yang ditemelkan di-encrypt saat penyimpanan dan tidak pernah meninggalkan server.",
+ "keyStoredKeep": "Kunci sudah disimpan — tempel yang baru untuk menggantikannya, atau biarkan kosong untuk mempertahankannya.",
+ "keyUploadFile": "Unggah file kunci",
+ "keyPasteInvalid": "File itu tidak terlihat seperti kunci privat yang valid.",
+ "passphrase": "Passphrase",
+ "optional": "(opsional)",
+ "passphrasePlaceholder": "Masukkan passphrase atau biarkan kosong",
+ "advanced": "Lanjutan",
+ "jumpHost": "Jump Host",
+ "extraArgs": "Argumen SSH Tambahan",
+ "testing": "Menguji…",
+ "connected": "Terhubung",
+ "testConnection": "Uji Koneksi",
+ "saveChanges": "Simpan Perubahan",
+ "saveAndContinue": "Simpan & Lanjutkan ke Setup",
+ "modalTitle": "Tambahkan server Anda",
+ "modalSubtitle": "Hubungkan via SSH. Anda dapat menyelesaikan setup nanti.",
+ "saveServer": "Simpan server",
+ "savingServer": "Menyimpan…",
+ "toastIpRequired": "Alamat IP server diperlukan",
+ "toastPasswordSwitchRequired": "Kata sandi diperlukan saat beralih ke autentikasi kata sandi",
+ "toastKeyPathSwitchRequired": "Jalur kunci diperlukan saat beralih ke autentikasi kunci SSH",
+ "toastKeyRequired": "Kunci privat diperlukan",
+ "toastUpdated": "Server diperbarui",
+ "toastSaved": "Server tersimpan",
+ "toastSaveFailed": "Gagal menyimpan server",
+ "toastPasswordTestRequired": "Kata sandi diperlukan untuk menguji koneksi",
+ "toastKeyPathTestRequired": "Jalur kunci diperlukan untuk menguji koneksi",
+ "toastConnectionSuccess": "Koneksi berhasil",
+ "toastConnectionFailed": "Koneksi gagal",
+ "toastConnectionTestFailed": "Uji koneksi gagal"
+ },
+ "detail": {
+ "tabs": {
+ "overview": "Ringkasan",
+ "migrations": "Migrasi",
+ "components": "Komponen",
+ "github": "Autentikasi Git",
+ "security": "Keamanan",
+ "ports": "Penerusan port",
+ "terminal": "Terminal"
+ },
+ "healthy": "Sehat",
+ "issues": "Masalah",
+ "sshPort": "SSH · {port}",
+ "edit": "Edit",
+ "removeServer": "Hapus Server",
+ "serverNotFound": "Server tidak ditemukan",
+ "serverNotFoundDesc": "Server ini tidak ada di workspace Anda, atau Anda tidak memiliki akses ke sana. Mungkin sudah dihapus.",
+ "noServerConfigured": "Tidak ada server yang dikonfigurasi dengan id \"{id}\".",
+ "editServer": "Edit Server",
+ "editSubtitle": "Perbarui detail koneksi untuk {name}",
+ "saveChanges": "Simpan Perubahan",
+ "connection": "Koneksi",
+ "host": "Host",
+ "port": "Port",
+ "user": "Pengguna",
+ "auth": "Autentikasi",
+ "authSshKey": "Kunci SSH",
+ "authPassword": "Kata Sandi",
+ "cancel": "Batal",
+ "remove": "Hapus",
+ "removeServerMessage": "Menghapus server ini juga menghapus setiap proyek dan aplikasi yang di-deploy di atasnya dari Openship.",
+ "removeComponentTitle": "Hapus {label}",
+ "removeOpenrestyMessage": "Ini menghapus container edge dari server. Sertifikat dan vhost tetap di host. Gunakan Instal Ulang saat Anda hanya perlu menyegarkan edge ke image terbaru.",
+ "removeComponentMessage": "Ini menghapus {label} dari server. Ini bersifat destruktif dan dapat menonaktifkan fitur deployment terkait sampai Anda menginstalnya kembali.",
+ "reinstallDockerTitle": "Instal ulang Docker di server ini?",
+ "reinstallDockerMessage": "Ini menjalankan installer resmi Docker, yang menarik engine terbaru — di server yang sudah terbaru ini merupakan upgrade besar. Menginstal atau meng-upgrade Docker me-restart daemon, dan itu me-restart setiap container di server ini, termasuk apa pun yang dijalankan Openship di sini. Tidak ada yang menginstal Docker untuk Anda: langkah lain melewatkannya saat sedang bekerja.",
+ "removalTakingLonger": "Penghapusan memakan waktu lebih lama dari yang diharapkan. Periksa status server.",
+ "removalTimedOutError": "Penghapusan habis waktu - periksa ulang server untuk melihat status saat ini.",
+ "toastComponentActionCompleted": "Tindakan komponen selesai",
+ "toastSomeActionsFailed": "Beberapa tindakan komponen gagal",
+ "toastHealthCheckFailedAfterInstall": "Health check gagal setelah instalasi",
+ "toastHealthCheckFailed": "Health check gagal",
+ "toastNoInstallableMissing": "Tidak ada komponen yang dapat diinstal yang hilang",
+ "toastServerMissing": "Server tidak ada",
+ "toastFailedStartInstall": "Gagal memulai instalasi",
+ "toastFailedRun": "Gagal menjalankan {label}",
+ "toastComponentRemoved": "{label} dihapus",
+ "toastRemovalTimedOut": "Penghapusan habis waktu - periksa ulang status server",
+ "toastFailedRemove": "Gagal menghapus {label}",
+ "toastServerRemoved": "Server dihapus",
+ "toastFailedRemoveServer": "Gagal menghapus server",
+ "removal": {
+ "title": "Hapus server",
+ "scanning": "Memeriksa apa yang di-deploy di server ini…",
+ "noWorkloads": "Tidak ada yang di-deploy di server ini. Menghapusnya menghapus pengaturan koneksi dan kredensial SSH yang disimpan.",
+ "introOne": "1 workload di-deploy di server ini dan akan dihapus dari Openship:",
+ "introOther": "{count} workload di-deploy di server ini dan akan dihapus dari Openship:",
+ "projectsOne": "1 proyek",
+ "projectsOther": "{count} proyek",
+ "appsOne": "1 aplikasi",
+ "appsOther": "{count} aplikasi",
+ "controlPlaneStays": "Openship itu sendiri berjalan di server ini dan tidak dihapus.",
+ "destroyLabel": "Juga hentikan dan hapus di server",
+ "destroyDesc": "Container dihentiakn dan dihapus serta volumenya dihapus. Tanpa ini, mereka tetap berjalan persis seperti saat ini.",
+ "destroyUnreachable": "Server ini tidak merespons, sehingga container-nya tidak dapat dihentikan dari sini. Mereka tetap berjalan dan hanya dihapus dari Openship.",
+ "destroyUnknown": "Status server ini tidak dapat diperiksa, sehingga container-nya tidak akan disentuh. Mereka tetap berjalan dan hanya dihapus dari Openship.",
+ "effectRecordOnly": "Container tetap berjalan di server. Openship berhenti mengelolanya — Anda dapat mengimpor ulang dari server ini nanti.",
+ "effectDestroy": "Container dihentkan, dihapus, dan volume datanya dihapus. Ini tidak dapat dibatalkan.",
+ "envVarsLost": "Variabel environment dihapus bersama proyek dan bukan bagian dari yang dapat diimpor ulang.",
+ "alsoTitle": "Juga dihapus bersama server ini",
+ "alsoMail": "Konfigurasi mail server",
+ "alsoTunnelsOne": "1 penerusan port",
+ "alsoTunnelsOther": "{count} penerusan port",
+ "alsoGithub": "Pendaftaran GitHub untuk server ini",
+ "alsoBackupsOne": "1 destinasi backup tidak akan lagi mengarah ke server ini",
+ "alsoBackupsOther": "{count} destinasi backup tidak akan lagi mengarah ke server ini",
+ "failedTitle": "Server dipertahankan — workload ini perlu diperhatikan terlebih dahulu",
+ "failedOrphaned": "beberapa sumber daya tidak dapat dihapus dan dimasukkan antrian untuk pembersihan",
+ "typePrefix": "Ketik ",
+ "typeSuffix": " untuk mengonfirmasi.",
+ "confirm": "Hapus server",
+ "confirmDestroy": "Hapus dan hapus workload",
+ "toastRemovedKeptOne": "Server dihapus. 1 workload dihapus dari Openship dan tetap berjalan.",
+ "toastRemovedKeptOther": "Server dihapus. {count} workload dihapus dari Openship dan tetap berjalan.",
+ "toastRemovedDestroyedOne": "Server dihapus. 1 workload dihapus dari mesin.",
+ "toastRemovedDestroyedOther": "Server dihapus. {count} workload dihapus dari mesin."
+ }
+ },
+ "banner": {
+ "unreachableTitle": "Tidak dapat menjangkau {host}",
+ "unreachableBody": "Openship tidak dapat membuka koneksi SSH ke {host}:{port}. Pastikan server berjalan dan sshd menerima koneksi di port {port}. Jika berada di balik firewall atau NAT, pastikan host dapat dijangkau dari mesin ini.",
+ "authTitle": "Kredensial SSH ditolak",
+ "authBody": "Berhasil menjangkau {host}:{port} tetapi server menolak autentikasi. Kunci atau kata sandi yang disimpan untuk server ini tidak cocok dengan yang diotorisasi di ~/.ssh/authorized_keys di host.",
+ "noServerTitle": "Konfigurasi server tidak lengkap",
+ "noServerBody": "Entri server ini kehilangan pengaturan SSH. Buka Edit untuk mengatur host, pengguna, dan kredensial, lalu jalankan health check.",
+ "hostChannelTitle": "Openship tidak dapat menjangkau host mesin ini",
+ "hostChannelBody": "Openship berjalan di container di sini dan berkomunikasi dengan host melalui SSH di {target}. Tidak ada yang merespons di sana, sehingga tidak dapat menjalankan operasi tingkat host di mesin ini.",
+ "hostChannelImpact": "Deploy biasa ke mesin ini tetap berfungsi — mereka melalui socket Docker. Diblokir sampai channel berfungsi: terminal host, info sistem host, dan pemindaian port host; mengambil alih :80/:443 dari proxy yang tidak dijalankan Openship; menginstal atau memperbarui mesin email; mende-deploy aplikasi katalog yang membutuhkan file konfigurasi yang dihasilkan di host (kong.yml Supabase, misalnya). Mesin ini juga menampilkan \"Offline\" di daftar server dashboard saat channel mati.",
+ "hostChannelFix": "Jalankan ini di host, lalu coba lagi:",
+ "hostChannelMissingTitle": "Openship tidak memiliki channel ke host mesin ini",
+ "hostChannelMissingBody": "Openship berjalan di container di sini, dan channel SSH yang digunakan untuk menjangkau host belum pernah disiapkan. Tidak ada alamat untuk dihubungi, sehingga tidak ada yang mati di sini — operasi tingkat host di mesin ini tidak memiliki tempat untuk dijalankan.",
+ "hostChannelWouldUse": "Setelah disiapkan channel akan menjangkau host di {target}. Belum ada yang menghubungi alamat itu.",
+ "hostChannelProvisionFix": "Jalankan ini di host untuk menyiapkan channel, lalu coba lagi:",
+ "hostChannelAuthTitle": "Host menolak kunci Openship",
+ "hostChannelAuthBody": "Openship berjalan di container di sini dan menjangkau host melalui SSH di {target}. Host merespons dan kemudian menolak kunci, sehingga operasi tingkat host tidak dapat dijalankan. Kunci tidak lagi diotorisasi untuk akun itu, atau sshd tidak mengizinkan akun itu untuk login sama sekali. Kredensial SSH yang disimpan di server ini tidak digunakan untuk koneksi ini — channel memiliki kunci sendiri.",
+ "hostChannelReauthorizeFix": "Jalankan ini di host untuk mengotorisasi ulang kunci channel, lalu coba lagi:",
+ "unknownTitle": "Health check gagal",
+ "unknownBody": "Openship tidak dapat berkomunikasi dengan server ini.",
+ "checkPowered": "Apakah VPS / VM menyala?",
+ "pingSuffix": "dari mesin ini - apakah ia merespons?",
+ "ncSuffix": "- apakah port {port} terbuka?",
+ "checking": "Memeriksa…",
+ "retry": "Coba lagi",
+ "editCredentials": "Edit kredensial",
+ "editServer": "Edit server",
+ "showRawError": "Tampilkan error mentah"
+ },
+ "overview": {
+ "cpu": "CPU",
+ "memory": "Memori",
+ "disk": "Disk",
+ "uptime": "Uptime",
+ "load": "Load {load1} · {load5} · {load15}",
+ "usageOf": "{used} dari {total}",
+ "sinceLastBoot": "sejak boot terakhir",
+ "components": "Komponen",
+ "checking": "Memeriksa…",
+ "allOperational": "Semua sistem beroperasi",
+ "unhealthyOf": "{unhealthy} dari {total} tidak sehat",
+ "noData": "Tidak ada data",
+ "healthy": "Sehat",
+ "unhealthy": "Tidak sehat",
+ "noHealthData": "Belum ada data kesehatan"
+ },
+ "components": {
+ "reinstall": "Instal Ulang",
+ "install": "Instal",
+ "running": "Berjalan...",
+ "unsupported": "Tidak didukung",
+ "remove": "Hapus",
+ "systemHealth": "Kesehatan Sistem",
+ "requiredComponents": "Komponen & layanan yang diperlukan",
+ "roleDocker": "Container runtime",
+ "roleGit": "Kontrol versi",
+ "roleOpenresty": "Reverse proxy",
+ "roleCertbot": "Sertifikat SSL",
+ "roleRsync": "Transfer file",
+ "installMissing": "Instal yang Hilang ({count})",
+ "allInstalled": "Semua terinstal",
+ "installing": "Menginstal…",
+ "removing": "Menghapus…",
+ "checking": "Memeriksa…",
+ "recheck": "Periksa ulang",
+ "runningHealthChecks": "Menjalankan health check…",
+ "noHealthData": "Belum ada data kesehatan",
+ "detectedInfrastructure": "Infrastruktur Terdeteksi",
+ "removalComplete": "Penghapusan Selesai",
+ "removalFinishedErrors": "Penghapusan Selesai dengan Kesalahan",
+ "installComplete": "Instalasi Selesai",
+ "installFinishedErrors": "Instalasi Selesai dengan Kesalahan",
+ "removalLogs": "Log Penghapusan",
+ "installLogs": "Log Instalasi",
+ "componentsCount": "{completed}/{total} komponen",
+ "dismiss": "Tutup",
+ "statusInstalling": "Menginstal…",
+ "statusRemoving": "Menghapus…",
+ "statusInstalled": "Terinstal",
+ "statusRemoved": "Dihapus",
+ "statusFailed": "Gagal",
+ "statusWaiting": "Menunggu"
+ },
+ "ports": {
+ "title": "Penerusan Port",
+ "descBefore": "Seperti VS Code: jangkau port server remote di",
+ "descAfter": " di mesin ini. Terikat ke loopback saja, dan aktif hanya saat diteruskan.",
+ "loading": "Memuat…",
+ "noForwards": "Belum ada penerusan.",
+ "running": "Berjalan",
+ "stopped": "Berhenti",
+ "stop": "Hentikan",
+ "start": "Mulai",
+ "remove": "Hapus",
+ "openOnStartup": "Buka saat startup",
+ "ariaOpenThisForward": "Buka penerusan ini saat startup",
+ "ariaOpenNewForward": "Buka penerusan baru saat startup",
+ "remotePortPlaceholder": "Port remote",
+ "localPortPlaceholder": "Lokal (otomatis)",
+ "add": "Tambah",
+ "toastRemotePortRange": "Port remote harus antara 1 dan 65535",
+ "toastLocalPortRange": "Port lokal harus 0 (otomatis) atau 1–65535"
+ },
+ "security": {
+ "title": "Rate Limiting",
+ "subtitle": "Rate limiting request OpenResty untuk server ini",
+ "loading": "Memuat...",
+ "couldntRead": "Tidak dapat membaca konfigurasi rate limit OpenResty",
+ "retryHint": "Coba lagi setelah memastikan OpenResty terinstal dan dapat dijangkau di server ini.",
+ "retry": "Coba lagi",
+ "refresh": "Segarkan",
+ "loadedFromLive": "Dimuat dari konfigurasi OpenResty yang aktif. Tekan edit untuk mengubahnya.",
+ "edit": "Edit",
+ "remove": "Hapus",
+ "editLiveHint": "Edit rate limit OpenResty yang aktif untuk server ini.",
+ "noLimitHint": "Belum ada rate limit yang aktif. Atur request per detik untuk membuat satu.",
+ "resetDraft": "Atur ulang draf",
+ "cancel": "Batal",
+ "requestsPerSecond": "Request / detik",
+ "burstAuto": "Burst dihitung secara otomatis: {burst}",
+ "burstCustom": "Override burst kustom aktif: {burst}",
+ "advanced": "Lanjutan",
+ "advancedHint": "Override perhitungan burst otomatis saat Anda membutuhkan kontrol yang lebih halus.",
+ "hideAdvanced": "Sembunyikan lanjutan",
+ "showAdvanced": "Tampilkan lanjutan",
+ "burstMode": "Mode burst",
+ "burstModeHint": "Otomatis menjaga burst tetap sinkron dengan request per detik. Manual memungkinkan Anda untuk meng-override-nya.",
+ "switchToManual": "Beralih ke manual",
+ "useAutomaticBurst": "Gunakan burst otomatis",
+ "burstAllowance": "Toleransi burst",
+ "burstAllowanceHint": "Request tambahan yang diantrekan tanpa penundaan.",
+ "whitelistedIps": "IP yang di-whitelist",
+ "whitelistHint": "IP ini melewati rate limiting. Loopback (127.0.0.1, ::1) selalu di-whitelist.",
+ "ipPlaceholder": "misal 145.223.101.50 atau 10.0.0.0/8",
+ "add": "Tambah",
+ "draftChanges": "Perubahan draf: {summary}",
+ "noDraftChanges": "Tidak ada perubahan draf",
+ "removeRateLimit": "Hapus rate limit",
+ "saveChanges": "Simpan perubahan",
+ "applyRateLimit": "Terapkan rate limit",
+ "policyBase": "{rps} req/s, burst {burst}",
+ "policyWhitelisted": "{base}, {count} di-whitelist",
+ "failedReadConfig": "Gagal membaca konfigurasi OpenResty",
+ "toastRateLimitUpdated": "Rate limit diperbarui: {from} -> {to}",
+ "toastFailedApply": "Gagal menerapkan",
+ "toastFailedSave": "Gagal menyimpan pengaturan rate limit",
+ "toastRemoved": "Rate limit dihapus",
+ "toastFailedRemove": "Gagal menghapus rate limit",
+ "toastInvalidIp": "Format IP/CIDR tidak valid",
+ "toastAlreadyWhitelisted": "Sudah ada di whitelist",
+ "ports": {
+ "title": "Pemaparan port",
+ "subtitle": "Lihat port mana yang mendengarkan dan dapat dijangkau dari jaringan",
+ "introHint": "Pindai setiap port yang mendengarkan di mesin ini dan tandai yang terikat ke antarmuka publik alih-alih loopback.",
+ "scanButton": "Pindai port",
+ "scanning": "Memindai port yang mendengarkan…",
+ "rescan": "Pindai ulang",
+ "retry": "Coba lagi",
+ "scanFailed": "Tidak dapat memindai port",
+ "scanFailedHint": "Server mungkin tidak dapat dijangkau. Coba lagi setelah online.",
+ "summary": "{total} port mendengarkan · {exposed} terekspos ke jaringan",
+ "summaryZeroExposed": "{total} port mendengarkan · tidak ada yang terekspos ke jaringan",
+ "exposed": "Terekspos",
+ "loopback": "Hanya Lokal",
+ "noProcessInfo": "Nama proses tidak tersedia (ss tidak terinstal di host ini).",
+ "unknownProcess": "Proses tidak diketahui",
+ "empty": "Tidak ditemukan port yang mendengarkan.",
+ "inconclusive": "Pindai tidak pasti",
+ "inconclusiveHint": "Tidak dapat membaca tabel soket di host ini. Coba lagi, atau periksa akses SSH.",
+ "reachable": "Dapat dijangkau",
+ "firewalled": "Diblokir firewall",
+ "expectedOpen": "Diharapkan terbuka",
+ "sensitiveHint": "Seharusnya tidak menghadap internet",
+ "sensitiveReachable": "Layanan sensitif dapat dijangkau dari internet",
+ "reachableLegend": "\"Dapat dijangkau\" dikonfirmasi dengan melakukan dial ke port dari luar mesin; \"Diblokir firewall\" terikat tetapi diblokir (misal oleh firewall cloud).",
+ "summaryReachable": "{total} port mendengarkan · {reachable} dapat dijangkau dari internet · {firewalled} diblokir firewall"
+ }
+ },
+ "terminal": {
+ "title": "Terminal",
+ "description": "Shell SSH langsung melalui WebSocket. Maksimal 3 bersamaan — salin saat dipilih."
+ },
+ "setup": {
+ "editServer": "Edit Server",
+ "addServer": "Tambah Server",
+ "enterDetails": "Masukkan detail server dan pilih metode autentikasi",
+ "gettingStarted": "Memulai",
+ "whatYouNeed": "Yang Anda butuhkan",
+ "needServer": "Server dengan akses SSH (Ubuntu, Debian, atau serupa)",
+ "needAuth": "Kunci SSH atau kata sandi untuk autentikasi",
+ "needChecks": "Setelah menyimpan, Openship akan menjalankan pemeriksaan dan menginstal prasyarat yang hilang untuk Anda.",
+ "yourServer": "server Anda",
+ "serverSetup": "Setup Server",
+ "subChoose": "Pilih cara Anda ingin menyiapkan server",
+ "subChecking": "Memeriksa persyaratan server…",
+ "subResults": "Hasil untuk {host}",
+ "subInstalling": "Menginstal komponen…",
+ "autoTitle": "Setup Otomatis",
+ "autoDesc": "Periksa persyaratan dan instal semuanya secara otomatis. Satu klik - Openship menangani Docker, OpenResty, dan Git.",
+ "stepTitle": "Setup Langkah demi Langkah",
+ "stepDesc": "Tinjau setiap komponen dan pilih apa yang akan diinstal. Lihat status detail sebelum melanjutkan.",
+ "skipTitle": "Lewati untuk Sekarang",
+ "skipDesc": "Tambahkan server tanpa menginstal apa pun — cukup pantau kesehatannya. Anda dapat menjalankan setup nanti dari halaman server.",
+ "connecting": "Menghubungkan ke server…",
+ "runningChecks": "Menjalankan health check sistem via SSH",
+ "connectionFailed": "Koneksi gagal",
+ "installing": "Menginstal…",
+ "installationFailed": "Instalasi gagal",
+ "ready": "Siap",
+ "missing": "Hilang",
+ "stepConnect": "Hubungkan",
+ "stepCheck": "Periksa",
+ "stepInstall": "Instal",
+ "statusInstalling": "Menginstal…",
+ "statusDone": "Selesai",
+ "statusFailed": "Gagal",
+ "statusWaiting": "Menunggu",
+ "overallProgress": "Progres Keseluruhan",
+ "connectingChecking": "Menghubungkan ke {host} dan memeriksa persyaratan…",
+ "allComponentsInstalled": "Semua Komponen Terinstal",
+ "setupFinishedErrors": "Setup Selesai dengan Kesalahan",
+ "installingComponents": "Menginstal Komponen",
+ "installedOf": "{count} dari {total} terinstal di {host}",
+ "settingUp": "Menyiapkan {host}…",
+ "allRequirementsMetTitle": "Semua Persyaratan Terpenuhi",
+ "allRequirementsMet": "Semua persyaratan terpenuhi",
+ "readyToDeploy": "{host} siap untuk di-deploy",
+ "installLogs": "Log Instalasi",
+ "line": "baris",
+ "lines": "baris",
+ "waitingForOutput": "Menunggu output…",
+ "doneGoToServers": "Selesai — Buka Server",
+ "goToServers": "Buka Server",
+ "retryFailed": "Coba Ulang Gagal ({count})",
+ "setupComplete": "Setup Selesai",
+ "installAllMissing": "Instal Semua yang Hilang",
+ "installMissingComponents": "Instal Komponen yang Hilang",
+ "recheck": "Periksa ulang",
+ "needAttention": "{count} komponen perlu diperhatikan",
+ "detectedInfrastructure": "Infrastruktur Terdeteksi",
+ "toastSetupCompleted": "Setup server selesai",
+ "toastSomeComponentsFailed": "Beberapa komponen gagal diinstal",
+ "toastSaveBeforeSetup": "Simpan server sebelum menjalankan setup",
+ "toastHealthCheckFailed": "Health check gagal",
+ "toastSaveBeforeInstall": "Simpan server sebelum menginstal komponen",
+ "toastAlreadyReady": "Server sudah siap",
+ "toastFailedStartInstall": "Gagal memulai instalasi"
+ },
+ "toastTitles": {
+ "server": "Server",
+ "serverSetup": "Setup Server",
+ "serverCheck": "Pemeriksaan Server",
+ "security": "Keamanan",
+ "portForwarding": "Penerusan Port"
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/id/settings.json b/apps/dashboard/src/i18n/locales/id/settings.json
new file mode 100644
index 000000000..14a09d714
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/id/settings.json
@@ -0,0 +1,1036 @@
+{
+ "language": {
+ "title": "Bahasa",
+ "description": "Pilih bahasa yang digunakan di seluruh dashboard."
+ },
+ "productView": {
+ "title": "Antarmuka",
+ "description": "Control plane mana yang ditampilkan oleh dashboard ini.",
+ "platformLabel": "Platform lengkap",
+ "platformDesc": "Proyek, aplikasi, deployment, server — semua yang bisa dilakukan instansi ini.",
+ "mailLabel": "Hanya mail",
+ "mailDesc": "Control plane mail di sidebar: domain, mailbox, DNS, antrean, log.",
+ "instanceDefault": "Instansi ini menggunakan default {mode}.",
+ "useDefault": "Gunakan default instansi"
+ },
+ "mailMode": {
+ "title": "Mode produk",
+ "description": "Instansi ini menampilkan dirinya sebagai apa. Ini adalah default untuk semua pengguna di sini — setiap pengguna tetap bisa memilih tampilannya sendiri di Pengaturan → Umum.",
+ "platformLabel": "Platform lengkap",
+ "platformDesc": "Proyek, aplikasi, deployment, server — semua yang bisa dilakukan instansi ini.",
+ "mailLabel": "Openship Mail",
+ "mailDesc": "Mengubah merek instansi menjadi Openship Mail dan menjadikan sidebar sebagai control plane mail.",
+ "savedOverridden": "Tersimpan. Tampilan Anda tidak berubah — Anda memilihnya di Pengaturan → Umum.",
+ "errorToast": "Gagal mengubah mode produk.",
+ "envNote": "Instansi ini mengikuti OPENSHIP_PRODUCT=mail dari environment. Memilih mode di sini menimpa pengaturan tersebut."
+ },
+ "common": {
+ "cancel": "Batal",
+ "save": "Simpan",
+ "done": "Selesai",
+ "close": "Tutup",
+ "back": "Kembali",
+ "disconnect": "Putuskan",
+ "remove": "Hapus",
+ "create": "Buat",
+ "previous": "Sebelumnya",
+ "next": "Berikutnya",
+ "loading": "Memuat…",
+ "copy": "Salin",
+ "copied": "Tersalin",
+ "toast": {
+ "cloud": "Cloud",
+ "defaults": "Default",
+ "settings": "Pengaturan",
+ "dns": "DNS",
+ "notifications": "Notifikasi",
+ "team": "Tim",
+ "members": "Anggota",
+ "invitation": "Undangan",
+ "invitations": "Undangan",
+ "permissions": "Izin",
+ "accessTokens": "Access token",
+ "cloneCredentials": "Kredensial clone",
+ "migration": "Migrasi",
+ "switchBack": "Kembali",
+ "export": "Ekspor",
+ "import": "Impor",
+ "importComplete": "Impor selesai",
+ "authUpgrade": "Peningkatan autentikasi",
+ "disconnect": "Putuskan"
+ }
+ },
+ "page": {
+ "title": "Pengaturan",
+ "subtitle": "Kelola preferensi, tim, dan konfigurasi instansi Anda",
+ "cloudConnectedToast": "Tersambung ke Openship Cloud"
+ },
+ "sidebar": {
+ "title": "Pengaturan",
+ "tabs": {
+ "general": "Umum",
+ "git": "Git",
+ "tokens": "Token API",
+ "mcp": "MCP",
+ "team": "Tim",
+ "notifications": "Notifikasi",
+ "email": "Email",
+ "systemSender": "Pengirim sistem",
+ "credentials": "Kredensial",
+ "cloud": "Cloud",
+ "infrastructure": "Infrastruktur",
+ "instance": "Instansi"
+ }
+ },
+ "email": {
+ "title": "Email (SMTP)",
+ "description": "Layanan SMTP yang digunakan instansi ini untuk mengirim email",
+ "intro": "Digunakan untuk reset password, verifikasi email, undangan tim, dan notifikasi. Cukup tempel kredensial SMTP dari penyedia mana pun — Amazon SES, Gmail, Mailgun, relay perusahaan — host, port, username, password. Tidak perlu menyiapkan server; Openship menghubungkannya langsung. Password dienkripsi dan tidak akan ditampilkan lagi.",
+ "loading": "Memuat…",
+ "configuredBadge": "Terkonfigurasi",
+ "notConfigured": "Belum diatur — reset password, undangan, dan notifikasi email tetap nonaktif sampai Anda memasukkan kredensial SMTP di bawah.",
+ "providerLabel": "Penyedia",
+ "hostLabel": "Host SMTP",
+ "hostPlaceholder": "smtp.example.com",
+ "portLabel": "Port",
+ "userLabel": "Username",
+ "userPlaceholder": "user@example.com",
+ "passwordLabel": "Password",
+ "passwordPlaceholder": "Password SMTP atau API key",
+ "passwordKeepPlaceholder": "•••••••• (biarkan kosong untuk mempertahankan yang saat ini)",
+ "fromLabel": "Alamat pengirim",
+ "fromPlaceholder": "Openship ",
+ "fromHint": "Opsional. Default ke username.",
+ "fromHintRequired": "Wajib — username Anda bukan email, jadi penerima memerlukan alamat pengirim yang valid.",
+ "fromRequired": "Masukkan alamat pengirim — username penyedia ini bukan email, jadi tidak bisa digunakan sebagai pengirim.",
+ "fromInvalid": "Alamat pengirim harus berisi email yang valid (contoh: Nama ).",
+ "save": "Simpan",
+ "saving": "Menyimpan…",
+ "saved": "Pengaturan SMTP tersimpan",
+ "saveFailed": "Gagal menyimpan pengaturan SMTP",
+ "hostRequired": "Masukkan host SMTP.",
+ "portInvalid": "Port harus berupa angka antara 1 dan 65535.",
+ "testTitle": "Kirim email uji coba",
+ "testPlaceholder": "anda@example.com",
+ "sendTest": "Kirim uji coba",
+ "sending": "Mengirim…",
+ "testSent": "Email uji coba terkirim — cek kotak masuk.",
+ "testFailed": "Uji coba gagal",
+ "saveFirst": "Simpan pengaturan SMTP Anda sebelum mengirim uji coba.",
+ "disable": "Nonaktifkan & hapus SMTP",
+ "disabled": "SMTP dinonaktifkan",
+ "toastTitle": "Email"
+ },
+ "github": {
+ "title": "GitHub",
+ "titleWithLogin": "GitHub · @{login}",
+ "noInstallations": "Tersambung · Belum ada instalasi",
+ "connectedOne": "Tersambung · 1 instalasi",
+ "connectedMany": "Tersambung · {count} instalasi",
+ "requiresCloud": "Membutuhkan Openship Cloud — App dimiliki oleh openship.io",
+ "connectPrompt": "Hubungkan akun GitHub Anda untuk mendeploy repository",
+ "checkingConnection": "Memeriksa koneksi…",
+ "addAccount": "Tambah akun",
+ "installApp": "Instal GitHub App",
+ "manageOnGithub": "Kelola di GitHub",
+ "disconnect": "Putuskan",
+ "disconnectTitle": "Putuskan {label}",
+ "disconnectAppLabel": "Openship GitHub App",
+ "disconnectAppBody": "Ini menghapus baris akun Openship OAuth. Instalasi GitHub App tetap ada sampai Anda menghapusnya di GitHub.",
+ "cloudExplainer": "Openship GitHub App dimiliki oleh openship.io. Hubungkan instansi Anda ke Openship Cloud untuk menggunakan token instalasi berskop untuk clone repo privat (berfungsi untuk deploy lokal DAN remote).",
+ "connectCloud": "Hubungkan Openship Cloud",
+ "noCloudTitle": "Clone repo privat tanpa cloud",
+ "useSshPerServer": "Kunci SSH per server",
+ "usePat": "Access token",
+ "noCloudHint": "Konfigurasi kunci SSH di setiap server (Server → GitHub), atau tambahkan personal access token yang berfungsi untuk deploy lokal dan remote.",
+ "linkExplainer": "Tautkan akun GitHub Anda untuk mengimpor repository, mengaktifkan auto-deploy saat push, dan mengelola branch langsung dari dashboard.",
+ "connecting": "Menghubungkan…",
+ "connect": "Hubungkan GitHub",
+ "orgBadge": "Org",
+ "userBadge": "Pengguna",
+ "ghCli": {
+ "title": "gh CLI",
+ "loggedInAs": "Masuk sebagai @{login}",
+ "fallbackDesc": "Fallback build lokal opsional untuk repo di luar instalasi App Anda",
+ "localOnly": "Build lokal saja",
+ "localOnlyTitle": "Ditolak saat deploy untuk build remote-server. App atau token clone per-proyek diperlukan untuk deploy remote.",
+ "usedForDeploys": "Digunakan untuk deploy",
+ "usedForDeploysTitle": "Tidak ada koneksi cloud — gh CLI adalah sumber utama untuk semua saat ini",
+ "activeWarnStrong": "gh CLI adalah sumber aktif.",
+ "activeWarnRest": "Deploy ke server remote akan ditolak — tambahkan kunci SSH per-server, hubungkan Openship App, atau setel token per-proyek untuk deploy ke target remote.",
+ "primaryNotePrefix": "Deploy remote menggunakan autentikasi GitHub masing-masing server (kunci SSH atau token), atau Openship App untuk cloud. gh CLI hanya mencakup",
+ "primaryNoteStrong": "build lokal",
+ "primaryNoteSuffix": "untuk repo di luar instalasi App Anda.",
+ "optionalPrefix": "Opsional. Jalankan",
+ "optionalSuffix": "di mesin ini untuk mengaktifkan clone build lokal repo di luar instalasi App Anda. Deploy remote menggunakan kunci SSH masing-masing server atau App.",
+ "disconnectPrefix": "Untuk memutuskan, jalankan",
+ "disconnectSuffix": "di terminal Anda.",
+ "use": "Gunakan gh CLI",
+ "login": "Masuk dengan GitHub",
+ "deviceHint": "Masukkan kode satu kali ini di GitHub untuk mengotorisasi instansi ini:",
+ "waiting": "Menunggu otorisasi — ini diperbarui secara otomatis.",
+ "tokenAltPrefix": "Lebih suka token? Tambahkan personal access token di",
+ "tokenAltLink": "Kredensial clone.",
+ "forwardOnNote": "Penerusan aktif — gh CLI juga mengautentikasi clone server remote. Identitas Anda dialirkan melalui SSH per-clone; tidak ada yang disimpan di server.",
+ "forwardOffHint": "Build lokal saja untuk saat ini. Aktifkan \"Teruskan identitas git saya ke server build\" untuk menggunakan gh CLI untuk deploy server remote juga.",
+ "manageForward": "Kelola penerusan",
+ "disconnectBody": "Openship akan menghapus kredensial ini dari instansi ini dan berhenti menggunakannya untuk clone. Kredensial tetap valid di GitHub sampai Anda mencabutnya di sana. Anda dapat menghubungkan ulang kapan saja.",
+ "recheck": "Periksa lagi"
+ },
+ "activeVia": "Tersambung melalui {method}",
+ "pickMethod": "Pilih bagaimana Openship mengakses repository Anda.",
+ "methodDevice": "Masuk GitHub",
+ "methodApp": "Openship Cloud GitHub App",
+ "methodAppDesc": "App terkelola — deploy ke server remote dengan token jangka pendek.",
+ "methodSshDesc": "Lampirkan kunci deploy ke setiap server (Server → GitHub).",
+ "methodTokenDesc": "Personal access token, dapat digunakan di mana saja.",
+ "signIn": "Masuk dengan GitHub",
+ "signInDesc": "Masuk browser dengan kode perangkat. Tidak perlu mendaftar, tidak perlu SSH ke server.",
+ "changeMethod": "Ubah metode",
+ "otherMethods": "Metode lain",
+ "openGithub": "Buka GitHub",
+ "copyCode": "Klik untuk menyalin",
+ "tokenConnect": "Hubungkan",
+ "tokenCreate": "Buat token di GitHub",
+ "tokenGhHint": "Sudah masuk di server dengan",
+ "tokenSaveFailed": "Gagal menyimpan token.",
+ "forwardOffHint": "Build lokal saja. Aktifkan \"Teruskan identitas git saya ke server build\" untuk menggunakannya untuk deploy server remote juga.",
+ "remoteCredentialHint": "Digunakan untuk penjelajahan repo dan build yang berjalan di host ini. Deploy yang build di server remote menggunakan kredensial server itu sendiri — tambahkan kunci deploy atau token per-server.",
+ "methodToken": "Token GitHub",
+ "methodHostCli": "gh CLI di host ini",
+ "manageTokensOnGithub": "Kelola token di GitHub",
+ "manageAccessOnGithub": "Kelola akses di GitHub",
+ "disconnectScopeNote": "Memutuskan menghapus kredensial ini dari Openship dan berhenti menggunakannya untuk clone. Kredensial tetap valid di GitHub sampai Anda mencabutnya di sana.",
+ "credentialRejected": "GitHub menolak {method} yang tersimpan.",
+ "credentialRejectedImpact": "Kredensial ini dicabut, kedaluwarsa, atau kehilangan cakupan yang diperlukan. Deploy yang clone dengannya akan terus gagal sampai Anda menghubungkan ulang di bawah.",
+ "credentialUnreachable": "Tidak dapat menghubungi GitHub untuk memeriksa {method} yang tersimpan.",
+ "credentialUnreachableImpact": "Ini biasanya masalah jaringan bukan kredensial yang buruk — Openship tetap menggunakannya. Menghubungkan ulang tidak akan membantu jika GitHub tidak dapat dijangkau.",
+ "credentialCheckedAt": "Terakhir diperiksa {time}"
+ },
+ "deployDefaults": {
+ "title": "Default Deploy",
+ "description": "Di mana deployment baru seharusnya dituju secara default",
+ "loading": "Memuat preferensi…",
+ "intro": "Target yang dipilih akan dipilih sebelumnya di picker deploy. Anda tetap bisa menimpanya per-deployment, atau menghapusnya agar ditanya setiap kali.",
+ "defaultServer": "Server default",
+ "noServers": "Belum ada server yang dikonfigurasi — tambahkan dari picker deploy.",
+ "clearDefault": "Hapus default (tanya setiap kali)",
+ "targets": {
+ "server": {
+ "label": "Server Saya",
+ "desc": "Server SSH yang sudah dikonfigurasi"
+ },
+ "cloud": {
+ "label": "OpenShip Cloud",
+ "desc": "Infra cloud terkelola"
+ }
+ },
+ "labelFor": {
+ "yourServer": "server Anda",
+ "cloud": "Openship Cloud"
+ },
+ "toast": {
+ "pickServer": "Pilih server terlebih dahulu",
+ "cleared": "Target deploy default dihapus",
+ "setTo": "Default diatur ke {label}",
+ "failed": "Gagal memperbarui default"
+ }
+ },
+ "buildPreferences": {
+ "title": "Preferensi Build",
+ "description": "Kontrol bagaimana proyek Anda dibangun dan dideploy",
+ "loading": "Memuat preferensi…",
+ "intro": "Pilih strategi build default untuk deployment baru. Override per-proyek tersedia di pengaturan proyek.",
+ "modes": {
+ "auto": {
+ "label": "Otomatis",
+ "desc": "Cerdas per-framework"
+ },
+ "server": {
+ "label": "Server",
+ "desc": "Build di remote"
+ },
+ "local": {
+ "label": "Lokal",
+ "desc": "Build di mesin"
+ }
+ },
+ "toast": {
+ "setTo": "Mode build diatur ke {mode}",
+ "failed": "Gagal memperbarui mode build"
+ }
+ },
+ "cloneCredentials": {
+ "title": "Kredensial clone GitHub",
+ "description": "Token personal yang digunakan untuk clone repo privat saat GitHub App belum diinstal",
+ "loading": "Memuat…",
+ "intro": "Tempel personal access token GitHub (classic atau fine-grained). Modul clone menggunakannya sebagai kredensial prioritas kedua — setelah override per-proyek, sebelum token instalasi GitHub App.",
+ "placeholder": "ghp_… atau github_pat_…",
+ "showToken": "Tampilkan token",
+ "hideToken": "Sembunyikan token",
+ "saveToken": "Simpan token",
+ "scopeHintPrefix": "Untuk repo privat, berikan token",
+ "scopeHintSuffix": "cakupan (classic) atau akses baca fine-grained ke repo yang ingin Anda deploy.",
+ "tokenSaved": "Token tersimpan",
+ "lastUpdated": "Terakhir diperbarui",
+ "justNow": "baru saja",
+ "replace": "Ganti",
+ "clear": "Hapus",
+ "useAsDefault": "Gunakan sebagai token clone default",
+ "useAsDefaultDesc": "Saat aktif, modul clone menggunakan token ini sebagai tier kedua dalam rantai (setelah override per-proyek). Saat nonaktif, token disimpan tetapi tidak digunakan — berguna jika Anda hanya ingin mengaktifkannya untuk deploy tertentu.",
+ "forwardGitLabel": "Teruskan identitas git saya ke server build",
+ "forwardGitDesc": "Saat build berjalan di server remote, teruskan login GitHub CLI lokal Anda melalui SSH tunnel agar repo privat clone langsung di server — tidak perlu GitHub App. Saat nonaktif, server menggunakan login gitnya sendiri, token yang tersimpan, atau jalur clone publik/lokal.",
+ "forwardGitFailed": "Gagal memperbarui penerusan git",
+ "toast": {
+ "pasteFirst": "Tempel token sebelum menyimpan",
+ "notLikeToken": "Itu tidak terlihat seperti token GitHub. Simpan saja jika Anda yakin.",
+ "saved": "Token clone tersimpan",
+ "saveFailed": "Gagal menyimpan token",
+ "cleared": "Token clone dihapus",
+ "clearFailed": "Gagal menghapus token",
+ "updateDefaultFailed": "Gagal memperbarui default"
+ }
+ },
+ "tokens": {
+ "title": "Personal Access Token",
+ "description": "Bearer token untuk API, CLI, dan klien MCP. Setiap token bertindak sebagai Anda.",
+ "explainer1": "Secara default sebuah token",
+ "explainerActsAsYou": "bertindak sebagai Anda",
+ "explainer2": "— token membawa izin peran Anda.",
+ "explainerReadOnly": "Hanya baca",
+ "explainer3": "memblokir semua tulisan, dan",
+ "explainerLimit": "Batasi ke resource tertentu",
+ "explainer4": "membatasi token persis ke apa yang Anda pilih — bahkan di bawah peran Anda sendiri. Ideal untuk token MCP yang sempit.",
+ "copyNow": "Salin token baru Anda sekarang",
+ "onlyTime": "Ini adalah satu-satunya kali token ditampilkan. Simpan di tempat yang aman.",
+ "namePlaceholder": "Nama token (contoh: Klien MCP)",
+ "readOnly": "Hanya baca",
+ "limitToResources": "Batasi ke resource tertentu",
+ "chooseResources": "Pilih resource",
+ "resourcesSelectedOne": "{count} resource dipilih",
+ "resourcesSelectedMany": "{count} resource dipilih",
+ "noResourcesSelected": "Belum ada resource yang dipilih",
+ "createToken": "Buat token",
+ "newToken": "Token baru",
+ "loading": "Memuat…",
+ "noActive": "Tidak ada token aktif.",
+ "badgeReadOnly": "hanya baca",
+ "badgeScoped": "berskop",
+ "metaLine": " · terakhir digunakan {lastUsed} · kedaluwarsa {expires}",
+ "callsOne": " · 1 panggilan",
+ "callsMany": " · {count} panggilan",
+ "revoke": "Cabut",
+ "expiry": {
+ "none": "Tidak ada kedaluwarsa",
+ "days30": "30 hari",
+ "days90": "90 hari",
+ "year1": "1 tahun"
+ },
+ "scopePicker": {
+ "title": "Cakupan token",
+ "subtitle": "Token dibatasi tepat pada resource ini. Anda hanya dapat memberikan akses yang Anda miliki sendiri.",
+ "saveLabel": "Selesai"
+ },
+ "toast": {
+ "giveName": "Beri nama token",
+ "pickResource": "Pilih setidaknya satu resource, atau nonaktifkan pembatasan cakupan",
+ "createFailed": "Gagal membuat token",
+ "revoked": "\"{name}\" dicabut",
+ "revokeFailed": "Gagal mencabut token",
+ "loadFailed": "Gagal memuat token"
+ }
+ },
+ "mcp": {
+ "title": "MCP",
+ "description": "Hubungkan agen AI ke Openship API Anda melalui Model Context Protocol.",
+ "loading": "Memuat…",
+ "connectAnother": "Hubungkan klien lain",
+ "bannerStrong": "Tempel endpoint di bawah ini ke klien MCP Anda",
+ "bannerRest": "(Claude, Cursor, …) — ini membuka jendela browser di mana Anda menyetujui akses dan memilih apa yang bisa diakses klien. Tidak perlu menyalin token. Openship adalah server OAuth 2.1 yang sesuai standar.",
+ "addToClient": "Tambahkan ke klien Anda",
+ "install": "Instal",
+ "endpoint": "Endpoint",
+ "endpointNote": "Streamable-HTTP JSON-RPC. Klien yang mendukung OAuth mengotorisasi di browser; saat disetujui Anda memilih hanya baca + resource mana yang dapat diakses klien.",
+ "authentication": "Autentikasi",
+ "oauth": "OAuth",
+ "staticToken": "Token statis",
+ "oauthNote": "Klien membuka browser untuk mengotorisasi — Anda menyetujui akses dan memilih apa yang bisa diakses. Tidak perlu menyalin.",
+ "tokenNote": "Untuk klien tanpa OAuth, atau saat Anda menginginkan token berskop tetap di konfigurasi.",
+ "clientConfig": "Konfigurasi klien",
+ "createTokenPrefix": "Buat token di tab",
+ "tokensTab": "Token",
+ "createTokenMid": "dan ganti",
+ "createTokenSuffix": "Token hanya-baca membatasi agen untuk operasi baca.",
+ "connectedClients": "Klien tersambung",
+ "clientReadOnly": "Hanya baca",
+ "clientFullControl": "Kontrol penuh",
+ "allResources": "Semua resource",
+ "resourcesOne": "{count} resource",
+ "resourcesMany": "{count} resource",
+ "orgPrefix": "{org} · ",
+ "authorized": "Diotorisasi {date}",
+ "lastUsedSuffix": " · terakhir digunakan {date}",
+ "callsOne": " · 1 panggilan",
+ "callsMany": " · {count} panggilan",
+ "toast": {
+ "disconnected": "Klien MCP terputus",
+ "disconnectFailed": "Gagal memutuskan"
+ },
+ "editAccess": "Edit akses",
+ "viewActivity": "Aktivitas",
+ "editScopeTitle": "Akses {client}",
+ "editScopeSubtitle": "Perubahan berlaku pada request berikutnya dari agen — tidak perlu menghubungkan ulang.",
+ "editScopeOrg": "Di {org}",
+ "wasNResourcesOne": "saat ini 1 resource",
+ "wasNResourcesMany": "saat ini {count} resource",
+ "saveScope": "Simpan akses",
+ "scopeSaved": "Akses diperbarui",
+ "scopeSaveFailed": "Tidak dapat memperbarui akses",
+ "loadScopeFailed": "Tidak dapat memuat akses klien ini",
+ "scopeGrantRejected": "Anda tidak lagi dapat memberikan {resource}, sehingga resource dihapus. Simpan lagi untuk melanjutkan.",
+ "confirmWidenTitle": "Ini menghapus semua batasan",
+ "confirmWidenBody": "Agen akan bertindak dengan akses penuh Anda, berlaku segera. Tekan lagi untuk mengonfirmasi.",
+ "confirmWidenAction": "Hapus semua batasan"
+ },
+ "team": {
+ "heading": "Tim",
+ "reachability": {
+ "description": "Rekan tim memerlukan URL publik untuk mengakses workspace ini.",
+ "notInstalledTitle": "Berikan Openship alamat publik",
+ "notInstalledBody": "Openship belum diatur dengan URL publik. Deploy dengan alamat publik (contoh `openship up --public-url …`), lalu tambahkan domain di sini agar rekan tim dapat mengaksesnya.",
+ "noDomainTitle": "Tambahkan domain ke Openship",
+ "noDomainBody": "Tambahkan domain ke aplikasi Openship agar rekan tim dapat mengakses workspace ini — undangan aktif secara otomatis setelah domain aktif.",
+ "addDomain": "Tambahkan domain",
+ "pendingTitle": "Selesaikan pengaturan domain Anda",
+ "pendingBody": "Domain Openship Anda sedang dalam verifikasi (DNS / SSL). Undangan aktif setelah domain aktif.",
+ "viewDomain": "Lihat domain"
+ },
+ "descPersonal": "Orang yang memiliki akses ke workspace pribadi Anda.",
+ "descTeam": "Orang yang memiliki akses ke proyek, deployment, dan server organisasi ini.",
+ "inviteMember": "Undang anggota",
+ "createTeamCard": {
+ "title": "Buat organisasi tim",
+ "body": "Anda dapat mengundang orang di sini langsung — atau membuat ruang bersama terpisah yang terisolasi dari proyek pribadi Anda.",
+ "button": "Buat tim"
+ },
+ "activeMembers": "Anggota aktif ({count})",
+ "you": "(Anda)",
+ "removeMember": "Hapus anggota",
+ "pendingInvitations": "Undangan tertunda ({count})",
+ "invitedAs": "Diundang sebagai {role} - kedaluwarsa {date}",
+ "roles": {
+ "owner": "Pemilik",
+ "admin": "Admin",
+ "member": "Anggota",
+ "restricted": "Terbatas"
+ },
+ "createTeamModal": {
+ "title": "Buat organisasi tim",
+ "body": "Workspace pribadi Anda tetap tidak tersentuh. Organisasi tim memiliki proyek, server, dan anggota yang diundang sendiri.",
+ "teamName": "Nama tim",
+ "placeholder": "Acme Inc",
+ "createTeam": "Buat tim"
+ },
+ "memberPanel": {
+ "subtitle": "Grant berlaku untuk anggota terbatas. Pemilik/admin/anggota memiliki akses luas ke organisasi secara default.",
+ "saveLabel": "Simpan akses"
+ },
+ "serverAccessWarning": {
+ "title": "Berikan akses server penuh?",
+ "body": "{member} akan mendapatkan kontrol penuh terhadap {scope} — setiap deployment dan datanya, semua aplikasi yang berjalan, rahasia dan environment yang tersimpan, akses terminal, dan integrasi apa pun yang tersambung (contoh: token GitHub dan kunci deploy yang dipegang untuk server). Mereka akan dapat mengelola atau menghapus resource ini.",
+ "scopeAllServers": "semua server dalam organisasi ini",
+ "scopeThisServer": "server ini",
+ "scopeCount": "{count} server",
+ "confirm": "Berikan akses"
+ },
+ "confirmRemove": "Hapus anggota ini dari organisasi?",
+ "workspace": {
+ "deleteWorkspace": "Hapus workspace",
+ "leaveWorkspace": "Tinggalkan workspace",
+ "deleteBody": "Hapus permanen workspace ini dan semua isinya.",
+ "leaveBody": "Tinggalkan workspace ini — Anda memerlukan undangan baru untuk kembali.",
+ "deleteConfirm": "Hapus workspace ini? Proyek, anggota, dan pengaturannya dihapus untuk semua orang. Ini tidak dapat dibatalkan.",
+ "leaveConfirm": "Tinggalkan workspace ini? Anda akan kehilangan akses sampai diundang lagi.",
+ "manage": {
+ "title": "Kelola workspace",
+ "sectionBody": "Ubah nama workspace ini, jeda proyeknya, atau hapus.",
+ "nameLabel": "Nama workspace",
+ "rename": "Ubah nama",
+ "projects": "Proyek ({count})",
+ "noProjects": "Tidak ada proyek di workspace ini.",
+ "running": "Berjalan",
+ "stopped": "Berhenti",
+ "pauseTitle": "Jeda semua",
+ "pauseBody": "Hentikan setiap proyek tetapi pertahankan workspace — mulai ulang kapan saja.",
+ "pauseButton": "Hentikan semua proyek",
+ "deleteTitle": "Hapus workspace",
+ "deleteBody": "Merobohkan semua {count} proyek (container dan route) dan menghapus workspace. Data volume tetap dapat dipulihkan. Tidak dapat dibatalkan.",
+ "confirmLabel": "Ketik \"{name}\" untuk mengonfirmasi",
+ "deleting": "Menghapus…",
+ "deletingProject": "Menghapus {name}…",
+ "deletingWorkspace": "Menghapus workspace…",
+ "toastRenamed": "Nama workspace diubah",
+ "toastRenameFailed": "Gagal mengubah nama workspace",
+ "toastPaused": "Menghentikan {count} proyek",
+ "toastPauseFailed": "Beberapa proyek tidak dapat dihentikan",
+ "toastProjectFailed": "Gagal menghapus {name}"
+ }
+ },
+ "toast": {
+ "teamNameRequired": "Nama tim wajib diisi",
+ "teamCreated": "Tim \"{name}\" dibuat — beralih sekarang…",
+ "createFailed": "Gagal membuat tim",
+ "loadFailed": "Gagal memuat tim",
+ "updateRoleFailed": "Gagal memperbarui peran",
+ "removeFailed": "Gagal menghapus",
+ "cancelFailed": "Gagal membatalkan",
+ "accessUpdated": "Akses diperbarui",
+ "loadGrantsFailed": "Gagal memuat grant",
+ "deleteFailed": "Gagal menghapus workspace",
+ "deleteBillingActive": "Batalkan langganan aktif workspace ini sebelum menghapus.",
+ "deleted": "Workspace dihapus",
+ "leaveFailed": "Gagal meninggalkan workspace",
+ "left": "Meninggalkan workspace"
+ }
+ },
+ "inviteMember": {
+ "headerTitle": "Undang anggota",
+ "headerDescRestricted": "Atur perannya di sebelah kiri, lalu pilih tepat apa yang bisa diakses di sebelah kanan.",
+ "headerDescDefault": "Kami akan mengirimkan email tautan untuk bergabung dengan organisasi ini.",
+ "email": "Email",
+ "emailPlaceholder": "rekan@example.com",
+ "role": "Peran",
+ "roleMemberTitle": "Anggota",
+ "roleMemberDesc": "Baca + tulis ke semua resource dalam organisasi ini.",
+ "roleAdminTitle": "Admin",
+ "roleAdminDesc": "Semua yang bisa dilakukan Anggota, ditambah mengelola anggota + penagihan.",
+ "roleRestrictedTitle": "Terbatas",
+ "roleRestrictedDesc": "Tidak ada akses default — pilih tepat resource mana yang akan dibuka.",
+ "grantsOne": "{count} grant",
+ "grantsMany": "{count} grant",
+ "sendVia": "Kirim melalui",
+ "yourMailServer": "Sistem mail Anda",
+ "openshipCloud": "Openship Cloud",
+ "mailReady": "Siap",
+ "mailNotSetUp": "Belum diatur",
+ "cloudReady": "Tersambung",
+ "cloudNotReady": "Tidak tersambung",
+ "noMailSystem": "Tidak ada sistem email yang diatur, sehingga undangan tidak akan dikirim.",
+ "setUpEmail": "Atur email →",
+ "cloudNotConnected": "Openship Cloud tidak tersambung, sehingga tidak dapat mengirim undangan.",
+ "connectCloud": "Hubungkan Openship Cloud →",
+ "pickerTitle": "Akses resource awal",
+ "pickerDesc": "Anggota terbatas dimulai tanpa akses. Pilih apa yang undangan ini buka setelah diterima — Anda dapat memberikan lebih nanti dari baris anggota.",
+ "sendInvite": "Kirim undangan",
+ "toast": {
+ "failedSend": "Gagal mengirim undangan",
+ "invitationSent": "Undangan dikirim ke {email}",
+ "personalOrgBlocked": "Workspace pribadi tidak dapat mengundang anggota. Buat organisasi tim terlebih dahulu, lalu kirim undangan.",
+ "updateMailSourceFailed": "Gagal memperbarui sumber mail"
+ }
+ },
+ "teamWorkspace": {
+ "title": "Undang tim Anda",
+ "description": "Pindahkan workspace ini ke tempat yang dapat dijangkau rekan tim Anda.",
+ "body": "Workspace ini hanya ada di komputer ini, sehingga rekan tim tidak dapat mengaksesnya dan undangan nonaktif. Pindahkan ke lokasi bersama — server Anda sendiri, Openship Cloud, atau tunnel — untuk mulai mengundang orang.",
+ "currentMode": "Mode saat ini",
+ "singleUserLocal": "Pengguna tunggal (lokal)",
+ "migrate": "Migrasi",
+ "ownerOnlyTitle": "Hanya pemilik workspace yang dapat migrasi",
+ "comingSoon": "Penautan langsung ke instansi remote akan segera hadir."
+ },
+ "cloud": {
+ "checking": "Memeriksa…",
+ "fallbackName": "Openship Cloud",
+ "connectedAccount": "Akun cloud tersambung",
+ "connected": "Tersambung",
+ "disconnect": "Putuskan",
+ "featuresActive": "Deploy cloud, SSL terkelola, dan CDN global aktif untuk instansi Anda.",
+ "connectToCloud": "Hubungkan ke Cloud",
+ "unlockDesktop": "Buka deploy cloud, SSL terkelola, dan CDN global.",
+ "deployLocal": "Deploy ke cloud langsung dari instansi lokal Anda.",
+ "waitingSignIn": "Menunggu masuk…",
+ "connectButton": "Hubungkan ke Openship Cloud",
+ "confirmDisconnect": "Putuskan dari Openship Cloud? Deploy cloud akan berhenti berfungsi sampai Anda menghubungkan ulang.",
+ "toast": {
+ "disconnected": "Terputus dari Openship Cloud",
+ "disconnectFailed": "Gagal memutuskan"
+ },
+ "pitch": {
+ "title": "Tambahkan Openship Cloud",
+ "body": "Setiap proyek mendapatkan domain .{domain} gratis dengan SSL otomatis — tanpa DNS, tanpa sertifikat. Server Anda sendiri tetap berfungsi seperti sekarang.",
+ "perkDomain": "Domain .{domain} gratis + SSL otomatis di setiap proyek",
+ "perkDeploy": "Deploy ke cloud terkelola, atau tetap di server Anda sendiri",
+ "perkServices": "Postgres, Redis, dan lainnya terkelola, disiapkan dengan sekali klik",
+ "perkGithub": "GitHub tersambung tanpa mendaftarkan aplikasi Anda sendiri",
+ "noLockIn": "Gratis untuk terhubung. Tidak ada yang berpindah, dan Anda dapat memutuskan kapan saja.",
+ "viewPricing": "Lihat harga"
+ }
+ },
+ "instance": {
+ "title": "Instansi",
+ "descDesktop": "Aplikasi desktop",
+ "descCloud": "Cloud",
+ "descSelfHosted": "Self-hosted",
+ "typeDesktop": "Desktop",
+ "typeCloud": "Openship Cloud",
+ "typeSelfHosted": "Self-Hosted",
+ "deployMode": "Mode deploy: {mode}",
+ "authNone": "Tanpa Autentikasi",
+ "authCloud": "Autentikasi Cloud",
+ "authLocal": "Autentikasi Lokal",
+ "localUser": "Pengguna lokal",
+ "change": "Ubah"
+ },
+ "upgradeAuth": {
+ "title": "Tambahkan password",
+ "description": "Tingkatkan instansi tanpa autentikasi ini ke login email + password yang nyata. Proyek dan riwayat Anda tetap utuh.",
+ "close": "Tutup",
+ "name": "Nama",
+ "namePlaceholder": "Jane Doe",
+ "email": "Email",
+ "emailPlaceholder": "anda@example.com",
+ "password": "Password",
+ "passwordPlaceholder": "Minimal 8 karakter",
+ "useMailServer": "Gunakan server mail Anda",
+ "useMailServerDesc": "Kirim reset password, verifikasi, dan undangan tim dari server mail yang telah Anda siapkan, bukan dari fallback SMTP berbasis environment.",
+ "createAccount": "Buat akun",
+ "toast": {
+ "passwordTooShort": "Password harus minimal 8 karakter",
+ "accountCreated": "Akun dibuat — Anda sekarang masuk",
+ "failedUpgrade": "Gagal meningkatkan"
+ }
+ },
+ "notifications": {
+ "channels": {
+ "title": "Channel pengiriman",
+ "description": "Ke mana notifikasi Anda dikirim. Channel yang terverifikasi dapat dihubungi; yang belum terverifikasi dilewati.",
+ "empty": "Belum ada channel. Tambahkan satu untuk mulai menerima notifikasi.",
+ "verified": "Terverifikasi",
+ "unverified": "Belum terverifikasi",
+ "sendTest": "Kirim uji coba",
+ "testSent": "Uji coba terkirim",
+ "deleteChannel": "Hapus channel",
+ "addChannel": "Tambah channel",
+ "confirmDelete": "Hapus channel ini? Langganan yang menggunakannya akan berhenti.",
+ "channelRemoved": "Channel dihapus",
+ "channelAdded": "Channel ditambahkan"
+ },
+ "describe": {
+ "slackWebhook": "Webhook Slack",
+ "discordWebhook": "Webhook Discord",
+ "msteamsWebhook": "Webhook Microsoft Teams",
+ "inApp": "Ikon lonceng dashboard"
+ },
+ "kinds": {
+ "email": "Email",
+ "webhook": "Webhook",
+ "slack": "Slack",
+ "discord": "Discord",
+ "msteams": "Microsoft Teams",
+ "telegram": "Telegram",
+ "in_app": "Dalam aplikasi"
+ },
+ "form": {
+ "labelPlaceholder": "Label (contoh: Email on-call)",
+ "emailPlaceholder": "anda@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": "Token bot dari @BotFather",
+ "telegramChatPlaceholder": "ID Chat (contoh: -1001234567890)",
+ "telegramHint": "Buat bot dengan @BotFather, lalu kirim pesan ke bot (atau tambahkan ke grup Anda) agar bot diizinkan membalas.",
+ "adding": "Menambahkan…",
+ "addChannel": "Tambah channel",
+ "labelRequired": "Label wajib diisi",
+ "noEmailTransport": "Tidak ada server email yang diatur di instansi ini, sehingga notifikasi email tidak akan dikirim.",
+ "setUpSmtp": "Atur SMTP →",
+ "otherAddress": "Alamat lain…"
+ },
+ "subscriptions": {
+ "title": "Tentang apa yang ingin diberitahukan",
+ "description": "Pilih event mana yang masuk ke channel mana. Setiap sel adalah satu langganan.",
+ "empty": "Tambahkan channel terlebih dahulu untuk berlangganan event.",
+ "allTab": "Semua",
+ "eventHeader": "Event",
+ "cellAria": "{category} melalui {channel}",
+ "notifyMe": "Beritahu saya"
+ },
+ "orgDefaults": {
+ "title": "Default organisasi",
+ "description": "Default yang diterapkan saat anggota baru bergabung dengan organisasi ini. Anggota dapat menimpa per-channel.",
+ "notifyAria": "Beritahu saat {category}",
+ "nChannels": "{n} channel"
+ }
+ },
+ "audit": {
+ "heading": "Log audit",
+ "subtitle": "Semua yang terjadi di organisasi ini, siapa yang melakukannya, dan kapan.",
+ "searchPlaceholder": "Cari aplikasi, server, domain, orang…",
+ "noMatch": "Tidak ada yang cocok dengan filter ini.",
+ "noEvents": "Belum ada yang tercatat.",
+ "clearFilters": "Hapus filter",
+ "showing": "Menampilkan {from}-{to} dari {total}",
+ "actor": "Aktor",
+ "name": "Nama",
+ "email": "Email",
+ "userId": "ID Pengguna",
+ "ip": "IP",
+ "userAgent": "User agent",
+ "resource": "Resource",
+ "type": "Tipe",
+ "id": "ID",
+ "changes": "Perubahan",
+ "before": "Sebelum",
+ "after": "Sesudah",
+ "removed": "Dihapus",
+ "system": "Sistem",
+ "actorPrefix": "Aktor {id}",
+ "recording": {
+ "title": "Catat aktivitas",
+ "description": "Simpan catatan setiap perubahan yang dibuat di organisasi ini. Aktif secara default.",
+ "paused": "Perekaman nonaktif. Tidak ada yang baru ditambahkan — entri di bawah ini adalah yang ditangkap sebelum perekaman dimatikan.",
+ "retention": "Simpan entri selama",
+ "retentionOption": "{days} hari",
+ "retentionHint": "Entri yang lebih lama dihapus secara otomatis.",
+ "adminOnly": "Hanya pemilik dan admin yang dapat mengubah ini.",
+ "saveFailed": "Tidak dapat menyimpan pengaturan aktivitas."
+ },
+ "filters": {
+ "anyone": "Siapa saja",
+ "anyAgent": "Agen mana pun",
+ "agentCalls": "{count} event",
+ "period": "Periode",
+ "allTime": "Semua waktu",
+ "today": "Hari ini",
+ "last7": "7 hari terakhir",
+ "last30": "30 hari terakhir",
+ "last90": "90 hari terakhir"
+ },
+ "tabs": {
+ "all": "Semua"
+ },
+ "sources": {
+ "all": "Semua",
+ "dashboard": "Dashboard",
+ "mcp": "Asisten AI",
+ "cli": "CLI",
+ "api": "API",
+ "webhook": "Webhook",
+ "system": "Sistem",
+ "unknown": "Tidak diketahui"
+ },
+ "day": {
+ "today": "Hari ini",
+ "yesterday": "Kemarin"
+ },
+ "details": {
+ "who": "Siapa",
+ "when": "Kapan",
+ "cameFrom": "Dari",
+ "viaAgent": "Agen",
+ "technical": "Detail teknis",
+ "eventType": "Tipe event"
+ }
+ },
+ "updates": {
+ "title": "Pembaruan",
+ "description": "Bagaimana Openship memeriksa dan menerapkan rilis baru",
+ "checking": "Memeriksa pembaruan…",
+ "available": "Openship {version} tersedia",
+ "upToDate": "Anda sudah menggunakan versi terbaru",
+ "current": "Saat ini: {version}",
+ "rerunToUpdate": " · jalankan ulang instalasi Anda untuk memperbarui",
+ "updateNow": "Perbarui sekarang",
+ "checkNow": "Periksa sekarang",
+ "autoUpdateLabel": "Pembaruan otomatis",
+ "autoUpdateDesc": "Unduh dan instal rilis baru secara otomatis. Nonaktif secara default — Anda tetap mengontrol kapan aplikasi Anda berubah.",
+ "notificationsLabel": "Notifikasi pembaruan & keamanan",
+ "notificationsDesc": "Tampilkan pemberitahuan saat rilis baru atau pengumuman keamanan mempengaruhi versi Anda. Pengumuman keamanan kritis selalu ditampilkan sekali, bahkan saat fitur ini nonaktif.",
+ "viewChangelog": "Lihat changelog & rilis",
+ "securityTitle": "Bagaimana pemeriksaan pembaruan bekerja",
+ "securityDescription": "Transparansi penuh tentang apa yang berbicara ke instalasi Anda",
+ "security1": "Openship memeriksa pembaruan dan pengumuman keamanan",
+ "securityOnlyGithub": "hanya dari rilis GitHub publik",
+ "security2": "dari repository sumber terbuka (",
+ "security3": "). Hanya",
+ "securityPulls": "menarik",
+ "security4": "data ini — tidak ada layanan pihak ketiga dan tidak ada server yang dioperasikan Openship yang dapat mendorong pembaruan atau sinyal ke instalasi Anda. Pengumuman dipasangkan dengan setiap rilis, sehingga perubahan yang belum dirilis tidak pernah sampai kepada Anda. Pengumuman keamanan kritis selalu ditampilkan sekali; sisanya mengikuti pengaturan di atas."
+ },
+ "infrastructure": {
+ "title": "Komponen platform",
+ "description": "Bagaimana container edge dan mail di server Anda tetap terbarui",
+ "autoUpdateLabel": "Perbarui infrastruktur server secara otomatis",
+ "autoUpdateDesc": "Saat control plane ini memperbarui, perbarui container edge dan mail di server yang tersambung secara otomatis. Nonaktif secara default — jika tidak, pembaruan ditandai untuk penerapan sekali klik di setiap server.",
+ "autoScanLabel": "Pindai infrastruktur server secara otomatis",
+ "autoScanDesc": "Periksa ulang server Anda secara diam-diam saat Anda membuka halaman Server atau halaman beranda, sehingga edge yang mati atau hilang ditandai tanpa pemindaian manual. Aktif secara default — hanya mendeteksi, tidak pernah mengubah apa pun.",
+ "attentionOne": "1 komponen memerlukan perhatian",
+ "attentionMany": "{n} komponen memerlukan perhatian",
+ "updatesOne": "1 pembaruan tersedia",
+ "updatesMany": "{n} pembaruan tersedia",
+ "allHealthy": "Semua komponen platform sudah terbaru",
+ "manageLink": "Kelola di Server",
+ "hostControlLabel": "Deploy ke mesin ini",
+ "hostControlDesc": "Izinkan OpenShip deploy aplikasi ke server tempat ia berjalan, bersama server yang tersambung. Nonaktif saat diinstal dengan --no-host-control (hanya control plane) — aktifkan untuk menambahkan mesin ini sebagai target deploy.",
+ "hostControlEnvNote": "Mesin ini diatur sebagai hanya control plane saat instalasi (--no-host-control). Mengaktifkan ini menimpa pengaturan tersebut sehingga OpenShip dapat deploy di sini.",
+ "hostControlError": "Tidak dapat mengubah host control — hanya pemilik workspace mesin ini yang dapat melakukannya."
+ },
+ "dataTransfer": {
+ "export": {
+ "title": "Ekspor data",
+ "description": "Unduh snapshot instansi portabel dengan riwayat yang dapat dipilih.",
+ "intro": "Membuat snapshot portabel dari konfigurasi, proyek, server, deployment, dan riwayat yang dipilih. Atel rahasia transfer untuk menyertakan kredensial; kredensial dienkripsi ulang sehingga file aman untuk dipindahkan. Biarkan kosong hanya saat Anda sengaja ingin ekspor tanpa kredensial.",
+ "filterTitle": "Riwayat opsional",
+ "filterDescription": "Konfigurasi, proyek, server, pengguna, dan kredensial selalu disertakan. Hapus centang riwayat yang tidak diperlukan untuk mengurangi ukuran ekspor.",
+ "filterAnalytics": "Analitik dan sampel penggunaan",
+ "filterActivity": "Riwayat audit dan notifikasi",
+ "filterBackups": "Riwayat backup dan pemulihan",
+ "filterIncidents": "Riwayat insiden kesehatan",
+ "filterMigrations": "Riwayat migrasi Docker",
+ "coreRows": "Baris yang selalu disertakan",
+ "selectedRows": "Baris ekspor yang dipilih",
+ "countUnavailable": "tidak tersedia",
+ "transferSecretNotice": "Untuk memindahkan kredensial dengan benar—termasuk password dan kunci SSH server, environment variable, token, dan kredensial backup—buat atau masukkan rahasia transfer di bawah. Simpan secara terpisah dan masukkan rahasia transfer yang sama saat impor. Jangan salin BETTER_AUTH_SECRET instansi Anda atau instal di tujuan.",
+ "generateTransferSecret": "Buat rahasia transfer",
+ "copyTransferSecret": "Salin rahasia transfer",
+ "copiedTransferSecret": "Rahasia transfer tersalin. Simpan dengan aman; Anda memerlukannya saat impor.",
+ "passphraseLabel": "Rahasia transfer (opsional hanya jika mengecualikan kredensial)",
+ "passphrasePlaceholder": "Buat atau masukkan rahasia transfer",
+ "confirmLabel": "Konfirmasi rahasia transfer",
+ "confirmPlaceholder": "Ulangi",
+ "mismatch": "Rahasia transfer tidak cocok.",
+ "exporting": "Mengekspor…",
+ "exportDownload": "Ekspor & unduh",
+ "toastWithSecrets": "Ekspor terunduh. Simpan rahasia transfer dengan aman — Anda memerlukannya untuk impor.",
+ "toastNoSecrets": "Ekspor terunduh. Kredensial TIDAK disertakan (tidak ada rahasia transfer yang diatur).",
+ "toastFailed": "Ekspor gagal."
+ },
+ "import": {
+ "title": "Impor data",
+ "description": "Pulihkan file yang diekspor ke instansi ini.",
+ "intro": "Muat file yang diekspor dari instalasi Openship lain. Jika berisi kredensial, masukkan rahasia transfer yang disimpan saat ekspor; tujuan mengenkripsi ulang dengan kunci instansinya sendiri.",
+ "importFromFile": "Impor dari file…",
+ "modalTitle": "Impor data instansi",
+ "modalSubtitle": "Pulihkan snapshot yang diekspor.",
+ "exportFile": "File ekspor",
+ "fileContainsSecrets": " · berisi kredensial (rahasia transfer diperlukan)",
+ "fileNoSecrets": " · tidak ada rahasia yang disertakan",
+ "passphrase": "Rahasia transfer",
+ "passphrasePlaceholder": "Rahasia transfer yang disimpan saat ekspor",
+ "mode": "Mode",
+ "modeReplaceTitle": "Ganti semua",
+ "modeReplaceDesc": "Hapus instansi ini dan pulihkan snapshot persis. Direkomendasikan untuk migrasi ke mesin baru.",
+ "modeMergeTitle": "Gabungkan dengan yang ada",
+ "modeMergeDesc": "Pertahankan data saat ini dan tambahkan data yang diimpor. Akun dan pengaturan Anda sendiri dipertahankan.",
+ "wipeWarn": "Ini menghapus semua data saat ini pada instansi ini dan membuat Anda keluar.",
+ "importing": "Mengimpor…",
+ "import": "Impor",
+ "confirmWipe": "Ganti SEMUA data di instansi ini dengan snapshot yang diimpor? Ini tidak dapat dibatalkan, dan Anda akan keluar.",
+ "notExport": "Itu tidak terlihat seperti file ekspor Openship.",
+ "cantRead": "Tidak dapat membaca file tersebut — apakah ini file ekspor yang valid?",
+ "importFailed": "Impor gagal.",
+ "rowsRestored": "{count} baris dipulihkan",
+ "secretsRestored": "{count} rahasia dipulihkan",
+ "secretsNotRestored": "rahasia TIDAK dipulihkan (tidak ada/salah passphrase) — masukkan kredensial lagi"
+ }
+ },
+ "migrate": {
+ "chooseTitle": "Pindah ke mode multi-pengguna",
+ "serverTitle": "Migrasi ke server Anda",
+ "cloudTitle": "Migrasi ke Openship Cloud",
+ "tunnelTitle": "Expose melalui tunnel",
+ "completeTitle": "Migrasi selesai",
+ "back": "Kembali",
+ "chooseIntro": "Saat ini instansi ini adalah single-user. Pilih bagaimana rekan tim Anda akan mengaksesnya.",
+ "cards": {
+ "serverTitle": "Migrasi ke server Anda",
+ "serverBody": "SSH-deploy openship di VPS yang Anda miliki. Data dipindahkan ke sana. Terbaik untuk tim yang sudah memiliki infrastruktur.",
+ "serverMeta": "Tanpa ketergantungan eksternal",
+ "cloudTitle": "Migrasi ke Openship Cloud",
+ "cloudBody": "Dorong data Anda ke api.openship.io. Kami menghosting semuanya. Terbaik untuk tim yang menginginkan zero infrastruktur.",
+ "cloudMetaConnected": "Akun cloud tersambung",
+ "metaRequiresCloud": "Membutuhkan akun cloud",
+ "cloudWarn": "Hubungkan ke Openship Cloud terlebih dahulu.",
+ "tunnelTitle": "Pertahankan lokal, expose melalui tunnel",
+ "tunnelBody": "Data Anda tetap ada di mesin ini. Kami menyiapkan tunnel edge Oblien sehingga rekan tim dapat mengakses Anda melalui internet publik — mesin Anda harus online.",
+ "tunnelMetaConnected": "Tunnel melalui Oblien",
+ "tunnelWarn": "Tunnel disiapkan melalui akun cloud Anda."
+ },
+ "server": {
+ "targetServer": "Server tujuan",
+ "loadingServers": "Memuat server...",
+ "noServersPrefix": "Belum ada server yang dikonfigurasi. Tambahkan di",
+ "pickServer": "Pilih server",
+ "domain": "Domain",
+ "freeSubdomain": "Subdomain gratis",
+ "freeSubdomainHint": "name.opsh.io",
+ "customDomain": "Domain kustom",
+ "customDomainHint": "DNS mengarah ke server",
+ "freeSlugPlaceholder": "myteam",
+ "customHostPlaceholder": "team.acme.com",
+ "preflight": "Preflight",
+ "sshReachable": "SSH dapat dijangkau",
+ "releaseAvailable": "Rilis Openship tersedia",
+ "domainReady": "Domain siap",
+ "runPreflight": "Jalankan preflight",
+ "recheck": "Periksa ulang",
+ "deploying": "Mendeploy...",
+ "startMigration": "Mulai migrasi",
+ "successDetail": "openship sekarang berjalan di server Anda. ID proyek {projectId}.",
+ "toastLoadFailed": "Gagal memuat server",
+ "toastPreflightFailed": "Preflight gagal",
+ "toastMigrationFailed": "Migrasi gagal"
+ },
+ "cloud": {
+ "connectFirstTitle": "Hubungkan akun cloud Anda terlebih dahulu",
+ "connectFirstBody": "Buka Pengaturan → Cloud dan masuk ke api.openship.io. Lalu kembali ke sini untuk migrasi.",
+ "whatHappens": "Apa yang terjadi saat Anda mengklik migrasi",
+ "bullet1": "Database lokal Anda didump (field terenkripsi dihapus — kredensial tidak meninggalkan mesin ini).",
+ "bullet2": "Dump diunggah ke api.openship.io di bawah organisasi aktif Anda.",
+ "bullet3": "Instansi lokal menjadi peluncur yang menunjuk ke app.openship.io.",
+ "bullet4": "Anda dapat beralih kapan saja — data lokal Anda tetap ada di disk.",
+ "proceedNonEmpty": "Lanjutkan bahkan jika organisasi cloud saya sudah memiliki proyek (saya akan menangani konflik)",
+ "pushingData": "Mendorong data...",
+ "startMigration": "Mulai migrasi",
+ "successDetail": "Mengimpor {projects} proyek, {deployment} deployment, {services} layanan.",
+ "toastConflict": "Organisasi cloud Anda sudah memiliki {count} proyek. Centang kotak untuk melanjutkan dan menyertakan data instansi ini bersama data yang sudah ada.",
+ "toastFailed": "Migrasi cloud gagal"
+ },
+ "tunnel": {
+ "connectFirstTitle": "Hubungkan akun cloud Anda terlebih dahulu",
+ "connectFirstBody": "Tunnel disiapkan melalui Openship Cloud. Buka Pengaturan → Cloud dan masuk, lalu kembali ke sini.",
+ "headsUp": "Perhatian",
+ "bullet1": "Data Anda tetap ada di mesin ini. Tidak ada yang berpindah.",
+ "bullet2": "Rekan tim hanya dapat mengakses Anda saat mesin ini online.",
+ "bullet3": "Agen tunnel berjalan di dalam proses API — tidak ada layanan tambahan yang perlu dijaga.",
+ "tunnelSlug": "Slug tunnel",
+ "slugPlaceholder": "myteam",
+ "slugSuffix": ".preview.oblien.com",
+ "slugHint": "3-32 karakter, huruf kecil, digit, tanda hubung. Harus dimulai dan diakhiri dengan huruf atau digit.",
+ "provisioning": "Menyiapkan...",
+ "provisionTunnel": "Siapkan tunnel",
+ "successDetail": "Tunnel \"{slug}\" mempublikasikan dashboard lokal Anda.",
+ "toastSlugTaken": "Slug \"{slug}\" sudah digunakan. Pilih yang lain.",
+ "toastNeedsCloud": "Tunnel membutuhkan akun cloud yang tersambung. Buka Pengaturan → Cloud terlebih dahulu.",
+ "toastFailed": "Penyiapan tunnel gagal"
+ },
+ "result": {
+ "complete": "Migrasi selesai",
+ "newLocation": "Lokasi baru",
+ "open": "Buka",
+ "done": "Selesai"
+ }
+ },
+ "switchBack": {
+ "nothingTitle": "Tidak ada yang bisa dialihkan",
+ "nothingBody": "Instansi ini sudah dalam mode single-user.",
+ "openDashboard": "Buka dashboard",
+ "resultTitle": "Kembali ke single-user",
+ "resultRestored": "Memulihkan {rows} baris dari remote ({mode}).",
+ "resultLocalTunnel": "Mode lokal dipulihkan. Tunnel dirobohkan — data Anda tidak pernah dipindahkan.",
+ "resultLocalGrace": "Mode lokal dipulihkan. Data remote dibiarkan utuh selama masa tenggang 30 hari.",
+ "relinkTitle": "Hubungkan ulang kredensial",
+ "relinkBody": "Kolom terenkripsi dihapus saat pemindahan lintas host. Hubungkan ulang ini sebelum diperlukan lagi:",
+ "relinkRowOne": "• {table}.{column} ({rows} baris)",
+ "relinkRowMany": "• {table}.{column} ({rows} baris)",
+ "heading": "Kembali ke single-user",
+ "intro": "Tarik instansi ini dari {mode} dan kembalikan ke mode solo di mesin ini.",
+ "modeServer": "server Anda",
+ "modeCloud": "Openship Cloud",
+ "modeTunnel": "tunnel",
+ "current": "Saat ini",
+ "noUrl": "(tidak ada URL yang tercatat)",
+ "teammatesLoseAccess": "Rekan tim kehilangan akses",
+ "warnTunnel": "Tunnel akan dirobohkan. Siapa pun yang menggunakan URL publik akan mendapat koneksi ditolak.",
+ "warnRemote": "Remote mempertahankan data Anda selama 30 hari sebagai masa tenggang jika Anda berubah pikiran.",
+ "dontPull": "Jangan tarik data remote — pertahankan apa yang ada di mesin ini saat ini.",
+ "dontPullDesc": "Gunakan ini saat remote tidak dapat dijangkau atau Anda sengaja ingin membuang perubahan remote.",
+ "back": "Kembali",
+ "tearingDown": "Merobohkan...",
+ "pullingData": "Menarik data...",
+ "switchBack": "Kembali",
+ "toastLoadStateFailed": "Gagal memuat state",
+ "toastRemoteUnreachable": "Remote tidak dapat dijangkau. Centang \"jangan tarik data remote\" untuk tetap kembali.",
+ "toastSwitchBackFailed": "Gagal kembali"
+ },
+ "routePreferences": {
+ "title": "Routing",
+ "description": "Bagaimana edge menjangkau upstream aplikasi Anda",
+ "loading": "Memuat preferensi…",
+ "intro": "Pilih addressing upstream default untuk proyek self-hosted baru. Override per-proyek ada di pengaturan proyek.",
+ "modes": {
+ "auto": {
+ "label": "Otomatis",
+ "desc": "Direkomendasikan (loopback port)"
+ },
+ "loopbackPort": {
+ "label": "Loopback port",
+ "desc": "Private 127.0.0.1 host port"
+ },
+ "containerIp": {
+ "label": "Container IP",
+ "desc": "Lanjutan — bridge IP"
+ }
+ },
+ "toast": {
+ "setTo": "Strategi route diatur ke {mode}",
+ "failed": "Gagal memperbarui strategi route"
+ }
+ },
+ "edgeOrphans": {
+ "title": "Route edge yang tidak terlacak",
+ "description": "Hostname yang masih dilayani mesin ini tetapi tidak memiliki catatan di Openship",
+ "scan": "Pindai",
+ "scanning": "Memindai…",
+ "allClear": "Setiap hostname yang dilayani edge sudah tercatat.",
+ "comparedTo": "Dibandingkan dengan {count} hostname yang dilacak.",
+ "unavailable": "Tidak dapat memeriksa edge",
+ "explain": "Menghapus proyek dengan \"Hapus dari Openship saja\" secara sengaja membiarkan routenya berjalan. Route aplikasi yang tertinggal kemudian mengembalikan 502 setelah container hilang, tetapi route STATIC yang tertinggal tetap melayani file lama dengan 200 — bahkan pada hostname yang telah diberikan ke proyek lain.",
+ "kindStatic": "File statis",
+ "kindProxy": "Route aplikasi",
+ "staticWarning": "Masih melayani file. Ini merespons request saat ini.",
+ "proxyWarning": "Menunjuk ke upstream yang mungkin sudah hilang.",
+ "servingFrom": "Melayani dari",
+ "proxyingTo": "Meneruskan ke",
+ "alsoAnswers": "Juga menjawab untuk",
+ "stopServing": "Hentikan pelayanan",
+ "stopping": "Menghentikan…",
+ "stopped": "{hostname} tidak lagi dilayani.",
+ "stopFailed": "Tidak dapat menghentikan pelayanan {hostname}",
+ "confirmTitle": "Hentikan pelayanan {hostname}?",
+ "confirmBody": "Ini hanya menghapus konfigurasi edge untuk hostname ini. File build tetap ada di disk, dan tidak ada yang lain di server yang disentuh. Jika proyek kemudian dideploy ke hostname ini, ia akan membuat route sendiri.",
+ "confirmCta": "Hentikan pelayanan",
+ "cancel": "Batal"
+ },
+ "credentials": {
+ "title": "Kredensial",
+ "description": "Token dan login yang Openship gunakan atas nama Anda — pull registry privat, tulis catatan DNS. Disimpan terenkripsi; tidak akan ditampilkan lagi setelah disimpan.",
+ "noProviders": "Tidak ada penyedia kredensial yang tersedia di instansi ini.",
+ "noneForProvider": "Belum ada yang ditambahkan.",
+ "add": "Tambah",
+ "edit": "Edit",
+ "save": "Simpan perubahan",
+ "connect": "Tambah kredensial",
+ "delete": "Hapus",
+ "verify": "Periksa ulang dengan penyedia",
+ "badgeActive": "Berfungsi",
+ "badgeInvalid": "Memerlukan perhatian",
+ "fieldName": "Label",
+ "namePlaceholder": "contoh: GHCR hanya-baca",
+ "secretKeptPlaceholder": "•••••••• (tersimpan — biarkan kosong untuk mempertahankan)",
+ "selectPlaceholder": "Pilih…",
+ "verifyNote": "Openship memeriksa kredensial dengan penyedia sebelum menyimpannya.",
+ "confirmDelete": "Hapus \"{name}\"? Apa pun yang mengandalkannya akan berhenti berfungsi.",
+ "toast": {
+ "loadFailed": "Tidak dapat memuat kredensial",
+ "created": "Kredensial ditambahkan",
+ "updated": "Kredensial diperbarui",
+ "deleted": "Kredensial dihapus",
+ "saveFailed": "Tidak dapat menyimpan kredensial",
+ "deleteFailed": "Tidak dapat menghapus kredensial",
+ "verifyOk": "Penyedia menerima kredensial ini",
+ "verifyFailed": "Penyedia menolak kredensial ini"
+ }
+ }
+}
diff --git a/apps/dashboard/src/i18n/locales/id/widgets.json b/apps/dashboard/src/i18n/locales/id/widgets.json
new file mode 100644
index 000000000..57f3be2c7
--- /dev/null
+++ b/apps/dashboard/src/i18n/locales/id/widgets.json
@@ -0,0 +1,436 @@
+{
+ "shared": {
+ "dnsRecords": {
+ "priority": "prioritas {n}",
+ "recommended": "direkomendasikan",
+ "name": "Nama",
+ "value": "Nilai",
+ "copy": "Salin"
+ },
+ "errorState": {
+ "commonCauses": "Penyebab umum",
+ "needHelp": "Butuh bantuan?",
+ "needHelpDesc": "Periksa dokumentasi atau hubungi kami jika masalah berlanjut.",
+ "documentation": "Dokumentasi",
+ "repoNotFound": {
+ "title": "Repository Tidak Ditemukan",
+ "subtitle": "Kami tidak dapat menemukan repository yang Anda cari.",
+ "hints": [
+ "Repository bersifat privat dan Anda belum memberikan akses",
+ "Nama repository atau pemilik salah eja",
+ "Repository telah dihapus atau dipindahkan",
+ "Koneksi GitHub Anda telah kedaluwarsa"
+ ],
+ "backToLibrary": "Kembali ke Pustaka",
+ "importRepository": "Impor Repository"
+ },
+ "projectNotFound": {
+ "title": "Proyek Tidak Ditemukan",
+ "subtitle": "Proyek yang Anda cari tidak ada atau telah dihapus.",
+ "hints": [
+ "Proyek telah dihapus",
+ "Anda tidak memiliki akses ke proyek ini",
+ "URL proyek tidak valid"
+ ],
+ "backToDashboard": "Kembali ke Dashboard",
+ "createNewProject": "Buat Proyek Baru"
+ },
+ "loadFailed": {
+ "title": "Gagal memuat proyek ini",
+ "subtitle": "Terjadi masalah saat mengakses proyek ini. Periksa koneksi Anda dan coba lagi."
+ },
+ "accessDenied": {
+ "title": "Akses Ditolak",
+ "subtitle": "Anda tidak memiliki izin untuk mengakses sumber daya ini.",
+ "hints": [
+ "Anda bukan pemilik proyek",
+ "Akun Anda tidak memiliki izin yang diperlukan",
+ "Keanggotaan tim Anda telah berubah"
+ ],
+ "backToDashboard": "Kembali ke Dashboard"
+ }
+ },
+ "machineSettings": {
+ "title": "Pengaturan Mesin",
+ "configureFor": "Konfigurasi sumber daya komputasi untuk",
+ "docs": "Dok",
+ "cpu": "CPU",
+ "memory": "Memori",
+ "storage": "Penyimpanan",
+ "base": "Dasar",
+ "current": "Saat ini",
+ "creditsPerMonth": "kredit/bulan",
+ "fromCurrent": "dari saat ini",
+ "viewPricing": "Lihat harga",
+ "cancel": "Batal",
+ "saving": "Menyimpan...",
+ "saveChanges": "Simpan Perubahan",
+ "toastUpdated": "Pengaturan mesin diperbarui",
+ "toastFailed": "Gagal memperbarui pengaturan"
+ },
+ "serverSelector": {
+ "serverLabel": "Server",
+ "loadingServers": "Memuat server...",
+ "noServerConnected": "Tidak ada server yang tersambung",
+ "connectServerFirst": "Tambahkan satu sekarang tanpa meninggalkan halaman ini.",
+ "addServer": "Tambah Server",
+ "selectServer": "Pilih server...",
+ "addNewServer": "Tambah server baru..."
+ }
+ },
+ "backup": {
+ "runCard": {
+ "streamError": "Gagal menyiarkan progres run: {message}",
+ "loading": "Memuat...",
+ "live": "Langsung",
+ "started": "Dimulai",
+ "finished": "Selesai",
+ "elapsed": "Berlalu",
+ "bytes": "Byte",
+ "runId": "ID run",
+ "status": {
+ "queued": "Dalam antrean",
+ "preparing": "Mempersiapkan",
+ "snapshotting": "Membuat snapshot",
+ "uploading": "Mengunggah",
+ "verifying": "Memverifikasi",
+ "succeeded": "Berhasil",
+ "failed": "Gagal",
+ "cancelled": "Dibatalkan",
+ "serverError": "Kesalahan server"
+ },
+ "phase": {
+ "queued": "Menunggu worker...",
+ "preparing": "Mengresolve tujuan + layanan...",
+ "snapshotting": "Menghasilkan artefak (pre-hook sedang berjalan)...",
+ "uploading": "Menyiarkan ke tujuan...",
+ "verifying": "Menulis manifest..."
+ }
+ },
+ "policyEditor": {
+ "presetHourly": "Per jam",
+ "presetDaily": "Harian (03:17)",
+ "presetWeekly": "Mingguan (Minggu 03:17)",
+ "presetMonthly": "Bulanan (Tgl 1 03:17)",
+ "presetManual": "Manual saja",
+ "selectDestinationAlert": "Pilih tujuan",
+ "failedSave": "Gagal menyimpan kebijakan",
+ "rotateConfirm": "Rotasi webhook token? URL saat ini akan berhenti berfungsi.",
+ "failedRotate": "Gagal merotasi token",
+ "editTitle": "Edit kebijakan backup",
+ "createTitle": "Buat kebijakan backup",
+ "serviceLabel": "Layanan: {name}",
+ "projectLevel": "Kebijakan tingkat proyek",
+ "destination": "Tujuan",
+ "selectOption": "— pilih —",
+ "schedule": "Jadwal",
+ "cronPlaceholder": "Cron kustom (5 field), atau biarkan kosong untuk manual saja",
+ "retainCount": "Jumlah retensi",
+ "retainCountHint": "Simpan N run berhasil terbaru",
+ "retainDays": "Retensi hari",
+ "retainDaysHint": "Hapus run yang lebih tua dari N hari",
+ "preDeployTrigger": "Pemicu pra-deploy",
+ "preDeployLabel": "Otomatis backup sebelum setiap deployment baru diterapkan",
+ "preHookHint": "Dijalankan pada setiap build berhasil, tepat sebelum kode baru live.",
+ "webhookTrigger": "Pemicu webhook",
+ "webhookLabel": "Aktifkan URL webhook masuk",
+ "webhookHint": "Menghasilkan token per-kebijakan. POST ke URL dari pemicu eksternal mana pun (GitHub Actions, monitoring, scheduler Anda sendiri).",
+ "copyUrl": "Salin URL",
+ "rotateToken": "Rotasi token",
+ "preHook": "Pre-hook (berjalan sebelum snapshot)",
+ "preHookHint": "Berjalan sebelum snapshot, sehingga skrip di sini dapat MENGHASILKAN yang akan di-backup — tulis dump ke folder (pg_dump > /tmp/dump/db.sql) danarsip folder tersebut dengan Files. Kegagalan membatalkan run, sehingga folder usang tidak pernah diambil seolah-olah baru.",
+ "postHook": "Post-hook (berjalan setelah unggah)",
+ "postHookHint": "Pembersihan; kegagalan dicatat tetapi tidak membatalkan run.",
+ "policyEnabled": "Kebijakan aktif",
+ "cancel": "Batal",
+ "saving": "Menyimpan...",
+ "saveChanges": "Simpan perubahan",
+ "createPolicy": "Buat kebijakan",
+ "methodLabel": "Metode backup",
+ "methodAuto": "Otomatis",
+ "methodAutoDesc": "Cerdas per layanan",
+ "methodVolume": "Volume",
+ "methodVolumeDesc": "Snapshot volume",
+ "methodCustom": "Kustom",
+ "methodCustomDesc": "Perintah Anda sendiri",
+ "detected": "Terdeteksi {label} — Otomatis akan membackupnya dengan {method}.",
+ "autoNoDb": "Tidak ada database yang terdeteksi — Otomatis akan snapshot volume layanan ini.",
+ "volumeHint": "Membuat tar untuk setiap volume layanan ini. Berjalan langsung, konsisten saat crash.",
+ "customCommandLabel": "Perintah dump",
+ "customCommandHint": "Berjalan di dalam container; stdout-nya menjadi artefak.",
+ "customCommandRequired": "Masukkan perintah dump untuk metode kustom.",
+ "restoreCommandLabel": "Perintah pemulihan",
+ "restoreCommandHint": "Menerima artefak pada stdin — kebalikan dari perintah dump. Wajib: tanpa ini, run ditangkap tetapi tidak pernah dapat dipulihkan.",
+ "restoreCommandRequired": "Masukkan perintah pemulihan — backup kustom yang ditangkap tanpa ini tidak pernah dapat dipulihkan.",
+ "advanced": "Opsi lanjutan",
+ "summaryTitle": "Ringkasan",
+ "summaryMethod": "Metode",
+ "summaryScheduleManual": "Manual saja",
+ "summaryLogical": "Dump {label}",
+ "summaryVolume": "Snapshot volume",
+ "summaryCustom": "Perintah kustom",
+ "retentionUnlimited": "Tanpa batas",
+ "keepN": "simpan {n}",
+ "olderThanN": "batas {n}h",
+ "methodDatabase": "Database",
+ "methodDatabaseDesc": "Dump logis",
+ "methodPath": "File",
+ "methodPathDesc": "Folder yang Anda tentukan",
+ "engineLabel": "Mesin",
+ "engineHint": "Memaksa alat dump ini bahkan ketika gambar tidak dikenali — misalnya Postgres yang dibangun kustom.",
+ "databaseHint": "Menjalankan {method} di dalam layanan. Dump logis: dapat dipulihkan ke versi berbeda, dan konsisten pada saat dimulai.",
+ "pathHint": "Mengarsip tepat folder yang Anda tentukan, dari dalam layanan yang berjalan. Konsisten saat crash — untuk salinan titik-waktu tertentu gunakan snapshot volume atau dump database.",
+ "pathsLabel": "Folder untuk diarsip",
+ "pathsHint": "Satu path absolut per baris, seperti yang terlihat di dalam layanan. Masing-masing menjadi artefak yang dapat dipulihkan.",
+ "pathsRequired": "Masukkan setidaknya satu path folder absolut untuk diarsip.",
+ "clearPathLabel": "Ganti saat pemulihan",
+ "clearPathHint": "Kosongkan folder sebelum ekstraksi, bukan menimpa yang ada. Ditolak untuk sistem dan direktori tingkat atas.",
+ "sourceIdsLabel": "Volume untuk disertakan",
+ "sourceIdsHint": "Satu nama volume atau path mount per baris. Biarkan kosong untuk semua volume dan bind mount yang dimiliki layanan ini.",
+ "quiesceLabel": "Jeda saat menyalin",
+ "quiesceHint": "Membekukan layanan untuk salinan yang konsisten. Tidak tersedia hingga penyalinan selesai.",
+ "excludeLabel": "Kecualikan",
+ "excludeHint": "Satu pola tar per baris, mis. ./cache atau *.sock.",
+ "compressionLabel": "Kompresi",
+ "compressionZstd": "zstd — terkecil",
+ "compressionGzip": "gzip — offline, lebih besar",
+ "compressionNone": "tidak ada — tanpa kompresi",
+ "compressionHint": "zstd mengompresi paling baik; gzip terpasang di setiap gambar, sehingga tidak perlu diunduh dan berfungsi di host yang terisolasi.",
+ "compressionAuto": "Otomatis — gunakan yang dimiliki layanan",
+ "summaryPath": "File dan folder"
+ }
+ },
+ "cloud": {
+ "connectionGate": {
+ "reconnect": "Sambungkan Ulang Openship Cloud",
+ "deployedOn": "Proyek ini di-deploy di Openship Cloud",
+ "sessionMissing": "Sesi Openship Cloud Anda tidak ada. Sambungkan untuk mengelola deployment, melihat log, dan mengakses kontrol runtime untuk {name}.",
+ "thisProject": "proyek ini",
+ "connecting": "Menyambungkan...",
+ "connect": "Hubungkan ke Openship Cloud",
+ "readOnlyNote": "Anda masih dapat menjelajahi halaman ini dalam mode read-only saat terputus."
+ }
+ },
+ "integrations": {
+ "githubPermission": {
+ "title": "Hubungkan ke GitHub",
+ "subtitle": "Pilih level akses repository",
+ "intro": "Pilih repository mana yang dapat diakses oleh Openship. Anda dapat mengubah ini nanti di pengaturan GitHub Anda.",
+ "publicTitle": "Repository publik saja",
+ "publicDesc": "Akses ke repository publik Anda. Cocok untuk proyek open source.",
+ "publicItem1": "Baca data repository publik",
+ "publicItem2": "Deploy repository publik",
+ "publicItem3": "Buat webhook untuk repo publik",
+ "allTitle": "Repository publik dan privat",
+ "allDesc": "Akses penuh ke semua repository Anda. Diperlukan untuk proyek privat.",
+ "allItem1": "Baca data repository publik dan privat",
+ "allItem2": "Deploy repository mana pun",
+ "allItem3": "Buat webhook untuk semua repo",
+ "allItem4": "Akses repository organisasi",
+ "noteLabel": "Catatan:",
+ "noteText": "Anda dapat mencabut akses kapan saja dari pengaturan GitHub di bawah Applications → Authorized OAuth Apps.",
+ "cancel": "Batal",
+ "connecting": "Menyambungkan...",
+ "connect": "Hubungkan ke GitHub"
+ }
+ },
+ "permissions": {
+ "resourcePicker": {
+ "failedLoadResources": "Gagal memuat sumber daya",
+ "searchPlaceholder": "Cari {type}...",
+ "allResources": "Semua {type}",
+ "noMatch": "Tidak ada sumber daya yang cocok dengan pencarian Anda.",
+ "noResourcesYet": "Belum ada {type} di organisasi ini.",
+ "selectedSummaryOne": "{count} sumber daya dipilih dari semua tipe.",
+ "selectedSummaryOther": "{count} sumber daya dipilih dari semua tipe.",
+ "wildcard": "wildcard",
+ "coveredByAll": "dicakup oleh semua",
+ "failedLoadOrgs": "Gagal memuat org GitHub",
+ "failedLoadRepos": "Gagal memuat repo untuk {login}",
+ "noOrgs": "Tidak ada organisasi GitHub yang tersambung.",
+ "collapse": "Ciutkan",
+ "expand": "Perluas",
+ "entireOrg": "seluruh org",
+ "repoCountOne": "{count} repo",
+ "repoCountOther": "{count} repo",
+ "coveredByOrg": "Dicakup oleh grant tingkat org di atas.",
+ "noRepos": "Tidak ada repository.",
+ "searchRepos": "Cari repo..."
+ },
+ "sourceAccess": {
+ "title": "Akses sumber",
+ "subtitle": "Apa yang dapat dibaca token ini dari {repo}",
+ "tierDeploy": "Deploy saja",
+ "tierDeployHint": "Metadata, branch, dan deteksi build. Tanpa isi file — ini adalah default.",
+ "tierContent": "Baca file",
+ "tierContentHint": "Baca isi file. Batasi ke path di bawah, atau biarkan kosong untuk semua.",
+ "tierWrite": "Baca dan tulis",
+ "tierWriteHint": "Juga memodifikasi file. Memerlukan izin tulis pada repository.",
+ "readPathsLabel": "Path yang dapat dibaca",
+ "writePathsLabel": "Path yang dapat ditulis",
+ "wholeRepo": "Semuanya",
+ "addPathPlaceholder": "src/** atau package.json",
+ "addPath": "Tambah",
+ "removePath": "Hapus {path}",
+ "errorInvalidPath": "Bukan pola yang valid. Gunakan src/** atau path file; .. dan path absolut tidak diizinkan.",
+ "errorDuplicatePath": "Pola tersebut sudah terdaftar.",
+ "problemWriteNeedsPermission": "Akses tulis memerlukan izin tulis pada repository.",
+ "summaryTitle": "Grant ini mengizinkan",
+ "summaryDeployOnly": "Mendeploy dan memeriksa. Tidak ada isi file yang dapat dibaca.",
+ "summaryReadWholeRepo": "Membaca setiap file.",
+ "summaryReadScoped": "Hanya membaca: {paths}",
+ "summaryWriteWholeRepo": "Menulis file apa pun.",
+ "summaryWriteScoped": "Hanya menulis: {paths}",
+ "summaryCloneAllowed": "Token clone, sehingga build lokal berfungsi.",
+ "summaryCloneBlocked": "Tanpa token clone — clone tidak dapat dibatasi ke path, sehingga build lokal tidak berfungsi.",
+ "apply": "Terapkan",
+ "cancel": "Batal",
+ "subtitleAccount": "Apa yang dapat dibaca token ini dari setiap repository di bawah {repo}",
+ "searchPaths": "Cari file dan folder...",
+ "treeLoading": "Memuat repository...",
+ "treeEmpty": "Repository ini tampak kosong.",
+ "treeNoMatch": "Tidak ada file atau folder yang cocok.",
+ "treeError": "Gagal memuat pohon repository.",
+ "treeCovered": "{path} sudah dicakup oleh aturan yang lebih luas di atas.",
+ "browseIn": "Jelajahi",
+ "appliesToEveryRepo": "Path berlaku untuk setiap repository di bawah akun ini.",
+ "selectedCount": "{count} dipilih",
+ "clearPaths": "Semuanya",
+ "advancedPattern": "Atau tambahkan pola",
+ "assigningTo": "Ditugaskan ke",
+ "surfaceRead": "Dapat dibaca",
+ "surfaceWrite": "Dapat ditulis",
+ "buttonHint": "Pilih file mana yang dapat dibaca grant ini",
+ "searchRepos": "Cari repository...",
+ "noRepoMatch": "Tidak ada repository yang cocok."
+ }
+ },
+ "routing": {
+ "domainSwitcher": {
+ "notConfigured": "Belum dikonfigurasi",
+ "selectDomain": "Pilih domain",
+ "allDomains": "Semua domain"
+ },
+ "publicEndpoints": {
+ "mappedToPort": "Dipetakan ke port {port}.",
+ "noPortYet": "Belum ada port yang dipilih.",
+ "mappedTo": "Dipetakan ke {path}.",
+ "exposedPort": "Port terekspos",
+ "staticPath": "Path statis",
+ "auto": "Otomatis",
+ "domain": "Domain",
+ "domainsRouted": "{count} domain dirutekan ke aplikasi ini.",
+ "accessibleWhere": "Di mana situs Anda akan dapat diakses",
+ "addDomain": "Tambah domain",
+ "primaryDomain": "Domain utama",
+ "domainN": "Domain {n}",
+ "remove": "Hapus"
+ },
+ "configEditor": {
+ "remove": "Hapus",
+ "removeHeader": "Hapus header",
+ "add": "Tambah",
+ "addHeader": "Tambah header",
+ "rewrites": "Rewrite",
+ "rewritesHint": "Menyajikan path/backend lain tanpa mengubah URL (mis. /api/(.*) → backend).",
+ "redirects": "Redirect",
+ "redirectsHint": "Redirect HTTP (301/308 permanen, selainnya 307).",
+ "headers": "Header",
+ "headersHint": "Header respons yang ditambahkan untuk path yang cocok.",
+ "permanent": "permanen",
+ "cleanUrls": "URL bersih",
+ "cleanUrlsHint": "(menghapus .html)",
+ "trailingSlash": "Trailing slash",
+ "proxyLimits": "Batasan request",
+ "proxyLimitsHint": "Ukuran unggah dan timeout. Jika kosong, default proxy diterapkan — unggahan 1 MB dan timeout 60d.",
+ "maxBodySize": "Ukuran unggah maks",
+ "maxBodySizeHint": "mis. 25m. Request lebih besar ditolak dengan 413. Default 1m.",
+ "readTimeout": "Timeout baca",
+ "readTimeoutHint": "mis. 300s. Berapa lama menunggu aplikasi merespons. Default 60d.",
+ "sendTimeout": "Timeout kirim",
+ "sendTimeoutHint": "mis. 300s. Berapa lama menunggu saat mengirim ke aplikasi. Default 60d.",
+ "bodyTimeout": "Timeout unggah",
+ "bodyTimeoutHint": "mis. 300s. Tingkatkan ini juga untuk unggahan besar, atau akan berhenti di tengah transfer. Default 60d.",
+ "proxyBuffering": "Buffer respons",
+ "proxyBufferingHint": "(matikan untuk streaming / SSE)",
+ "gzip": "Kompres respons",
+ "gzipHint": "(gzip teks, JSON, JS, CSS)",
+ "proxyGroups": {
+ "body": "Body request",
+ "timeouts": "Timeout upstream",
+ "buffering": "Buffering respons",
+ "compression": "Kompresi",
+ "headers": "Header & protokol",
+ "upstreamTls": "TLS ke aplikasi"
+ },
+ "proxyInherit": "default nginx",
+ "proxyNotSet": "belum diatur",
+ "proxyLiveLabel": "Menyajikan:",
+ "proxyLiveVaries": "berbeda per host",
+ "proxyDrift": "berbeda dari yang tersimpan",
+ "proxyDriftSummary": "Edge menyajikan nilai berbeda untuk:",
+ "proxyAdoptLive": "Gunakan nilai live",
+ "proxyLiveUnavailable": "Gagal membaca config live dari server proyek ini.",
+ "proxyLiveSource": "Dibaca dari:",
+ "proxyRefreshLive": "Baca ulang config live"
+ },
+ "settingsCard": {
+ "recordLabels": {
+ "CNAME": "Merutekan lalu lintas melalui jaringan edge",
+ "A": "Menunjuk ke IP server Anda",
+ "TXT": "Memverifikasi kepemilikan domain"
+ },
+ "publiclyExposed": "Terekspos publik",
+ "internalOnly": "Internal saja",
+ "freeSubdomain": "Subdomain gratis",
+ "free": "Gratis",
+ "customDomain": "Domain kustom",
+ "custom": "Kustom",
+ "save": "Simpan",
+ "checkingDns": "Memeriksa DNS...",
+ "addRecordHint": "Tambahkan record {primary} dan {txt}",
+ "viewRecords": "Lihat record",
+ "redirectTo": "Redirect",
+ "redirectServeApp": "Sajikan aplikasi di sini",
+ "redirectToHost": "→ {hostname}",
+ "redirectPermanent": "301 permanen",
+ "redirectTemporary": "302 sementara",
+ "exposedPort": "Port terekspos",
+ "staticPath": "Path statis",
+ "staticPathHint": "Sajikan file dari subdirektori ini di dalam output build. Gunakan {path} untuk output root.",
+ "dnsConfiguration": "Konfigurasi DNS",
+ "addRecordsFor": "Tambahkan record ini untuk {hostname}",
+ "nameHost": "Nama / Host",
+ "valueTarget": "Nilai / Target",
+ "dnsHintSelfhosted": "Tambahkan record A yang menunjuk ke IP server Anda, lalu record TXT untuk verifikasi.",
+ "dnsHintCloud": "Tambahkan record CNAME untuk routing, lalu record TXT untuk verifikasi.",
+ "dnsPropagation": "Perubahan DNS dapat memakan waktu hingga 48 jam."
+ }
+ },
+ "updates": {
+ "severity": {
+ "critical": "Kritis",
+ "recommended": "Direkomendasikan",
+ "info": "Saran"
+ },
+ "viewChangelog": "Lihat changelog",
+ "available": "tersedia",
+ "youreOn": "· Anda menggunakan {version}",
+ "rerunInstall": "Jalankan ulang instalasi Anda untuk memperbarui server self-hosted.",
+ "updateNow": "Perbarui sekarang",
+ "downloading": "Mengunduh Openship {version}",
+ "installing": "Menginstal — Openship akan memulai ulang",
+ "updateFailed": "Pembaruan gagal",
+ "retry": "Coba lagi",
+ "changelog": "Changelog",
+ "updatedTo": "Diperbarui ke Openship {version}",
+ "seeChanges": "Lihat perubahan di rilis ini.",
+ "onLatest": "Anda sudah menggunakan versi terbaru.",
+ "whatsNew": "Yang baru",
+ "whatsNewIn": "Yang baru di {version}",
+ "close": "Tutup",
+ "noNotes": "Tidak ada catatan rilis untuk versi ini.",
+ "viewOnGithub": "Lihat di GitHub",
+ "dismiss": "Tutup"
+ }
+}
diff --git a/docs/i18n/README.id.md b/docs/i18n/README.id.md
new file mode 100644
index 000000000..75237cb76
--- /dev/null
+++ b/docs/i18n/README.id.md
@@ -0,0 +1,207 @@
+Openship
+
+
+ Platform deployment open-source dan self-hosted dengan CI/CD bawaan.
+ Arahkan ke repository — ia akan membangun, mengirimkan, merutekan, dan mengakhiri TLS untuk aplikasi Anda. Kendalikan dari aplikasi desktop, dashboard web, atau CLI.
+
+
+
+
+
+
+
+
+
+ Mulai Cepat ·
+ Cara Kerja ·
+ Antarmuka ·
+ Dokumentasi ·
+ Berkontribusi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+---
+
+## Mulai Cepat
+
+Ada satu keputusan yang harus diambil terlebih dahulu: **cara menjalankan OpenShip itu sendiri** (control plane). Sisanya sama setelahnya.
+
+| Jika Anda… | Jalankan OpenShip sebagai | Di mana aplikasi Anda berjalan |
+|---|---|---|
+| **Sendirian, satu mesin, tanpa operasional** | **Aplikasi desktop** | Server yang Anda hubungkan melalui SSH, atau OpenShip Cloud |
+| **Tim — atau Anda ingin push-to-deploy / meng-host aplikasi di server sendiri** | **Server self-hosted** (`openship up`) | Di server tersebut (mode Compose) — atau ke server lain / Cloud (mode bare) |
+| **Tidak tertarik menjalankan apa pun** | **OpenShip Cloud** | Sandbox terkelola, zero setup |
+
+> [!TIP]
+> **Sendirian? Gunakan aplikasi desktop.** Aplikasi ini menjalankan control plane OpenShip di mesin Anda sendiri *hanya saat aplikasi terbuka* — tidak ada yang ditinggal berjalan di server yang selalu aktif, tidak ada yang diekspos secara publik. Anda hanya perlu instalasi server yang selalu aktif ketika Anda ingin **push-to-deploy (CI/CD)**, **akses tim**, atau **menghost aplikasi di server tersebut** — hal-hal yang memerlukan endpoint publik yang selalu aktif.
+
+### Sendirian — aplikasi desktop
+
+Control plane berjalan secara lokal dan mengendalikan server Anda melalui SSH. Tanpa login, tanpa terminal, tanpa permukaan publik — unduh, buka, selesai:
+
+| Platform | Unduh |
+|---|---|
+| **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) |
+
+Linux: `chmod +x Openship.AppImage && ./Openship.AppImage`. Sudah punya CLI? `openship install` mengunduh dan menjalankannya. Tautan selalu menunjuk ke rilis terbaru.
+
+Dari aplikasi desktop Anda menghubungkan server (SSH) atau OpenShip Cloud dan melakukan deploy ke sana — aplikasi itu sendiri tidak meng-host aplikasi publik di laptop Anda.
+
+### Tim / selalu aktif — server self-hosted
+
+Instal CLI (yang mencakup API + dashboard), lalu jalankan **`openship`** — wizard interaktif membuat admin pertama, menghubungkan domain Anda, dan menginstal OpenShip sebagai layanan boot. Jalankan lagi kapan saja untuk mengelola instance.
+
+```bash
+curl -fsSL https://get.openship.io | sh # instalasi (atau: npm i -g openship — butuh Node 22+)
+openship # pengaturan terpandu, lalu control panel
+```
+
+Untuk CI / server headless, lewati wizard dan jalankan `openship up` langsung:
+
+```bash
+openship up # instalasi + mulai sebagai layanan latar belakang (boot + restart otomatis)
+openship up --public-url https://openship.example.com # + layani dashboard di domain Anda (edge + TLS ditangani)
+```
+
+`openship open` membuka dashboard · `openship stop` menghentikannya · `openship update` memperbarui · `openship up --foreground` berjalan terpasang.
+
+**Deploy proyek:**
+
+```bash
+cd your-project
+openship init # hubungkan direktori ini ke proyek
+openship deploy
+```
+
+Panduan server lengkap + referensi CLI: **[openship.io/docs](https://openship.io/docs)**.
+
+
+Self-host dengan Docker Compose mentah (tanpa CLI)
+
+Stack self-hosted berada di **`docker/docker-compose.yml`** dan **menarik** gambar yang dipublikasikan dari GitHub Container Registry (`ghcr.io/oblien/*`) — tanpa alat build, tanpa kompilasi monorepo. Jalankan dari root repo:
+
+```bash
+git clone https://github.com/oblien/openship.git && cd openship
+cp .env.example .env # lalu edit
+docker compose --env-file .env -f docker/docker-compose.yml up -d
+```
+
+Stack-nya adalah **postgres + redis + api + dashboard + edge**. `edge` adalah OpenResty di **:80/:443** sebagai container (`network_mode: host`) — routing + Let's Encrypt, tanpa instalasi host bare. **Hanya Linux** (host networking); di mac/win gunakan `openship up` (bare). Container `api` me-mount Docker socket host sehingga control plane bisa membangun + menjalankan aplikasi Anda sebagai container host — ia memiliki hak istimewa host melalui socket, jadi jalankan hanya di host tepercaya.
+
+
+
+---
+
+## Cara Kerja
+
+Arahkan OpenShip ke sumber — **repository GitHub**, **folder lokal**, atau **artefak prebuilt** — dan ia menjalankan satu pipeline dari awal hingga akhir:
+
+1. **Deteksi.** Membaca `package.json`, konfigurasi framework, lockfile, dan `docker-compose.yml` / `openship.json` apa pun untuk menentukan stack, manajer paket, command build/mulai, dan port. Tanpa file konfigurasi yang diperlukan; `opsional.json` mengganti tebakan jika Anda ingin mengontrol.
+2. **Bangun.** Di server target atau lokal di orkestrator, menjadi gambar Docker atau rilis bare. Konfigurasi yang diselesaikan dibekukan menjadi snapshot, sehingga deploy ulang dan rollback menjalankan *persis* apa yang dikirim.
+3. **Jalankan.** Sebagai container (dipublikasikan di loopback saja — tidak pernah port publik) atau proses host yang diawasi.
+4. **Rutekan + amankan.** Edge OpenResty menulis vhost reverse-proxy ke domain Anda dan menerbitkan sertifikat Let's Encrypt (HTTP-01). Karena routing dan TLS terjadi *setelah* aplikasi aktif, gangguan DNS atau sertifikat muncul sebagai "aksi diperlukan" — ia tidak pernah gagal dalam deploy atau membawa aplikasi Anda turun.
+5. **Push-to-deploy.** Webhook GitHub menjalankan ulang pipeline setiap push ke branch yang dilacak — hanya membangun ulang layanan yang benar-benar disentuh oleh push monorepo.
+
+Database, domain, SSL, CDN, email, dan backup dikelola dari tempat yang sama. (Push-to-deploy dan domain publik memerlukan server yang selalu aktif atau Cloud — instance desktop/loopback tidak memiliki endpoint publik untuk menerima webhook.)
+
+---
+
+## Antarmuka
+
+Tiga cara untuk mengendalikan backend yang sama:
+
+- **Aplikasi desktop** — GUI lengkap, log real-time, satu klik untuk semuanya. Terbaik untuk penggunaan sendirian.
+- **Dashboard web** — UI yang sama di browser, dibuat untuk tim.
+- **CLI** — dapat di-scipting dan ramah CI; juga cara Anda menginstal dan mengelola instance self-hosted.
+
+Endpoint **MCP** (untuk AI agent) dan **REST API** melengkapi kebutuhan otomatisasi. Hanya route yang memilih untuk ikut yang diekspos sebagai tool MCP, setiap panggilan memeriksa ulang izin Anda, dan route credential/token tidak pernah bisa menjadi tool. Referensi lengkap di [openship.io/docs](https://openship.io/docs).
+
+> [!NOTE]
+> Dokumentasi sedang aktif diisi. Jika ada yang kurang atau tidak jelas, [kontribusi](../../CONTRIBUTING.md) sangat kami hargai.
+
+---
+
+## Fitur
+
+| | |
+|---|---|
+| **CI/CD bawaan** | Push-to-deploy, environment pratinjau, alur staging/produksi, rollback |
+| **Stack apa pun** | Node, Python, Go, Rust, PHP, Ruby, Java, .NET, Docker, monorepo |
+| **Backend lengkap** | Postgres, MySQL, MongoDB, Redis, worker, WebSocket, penyimpanan |
+| **Domain & SSL** | Let's Encrypt otomatis, wildcard, domain tak terbatas, perpanjangan otomatis |
+| **CDN** | Caching edge, HTTP/3, kompresi Brotli, purge instan |
+| **Server email** | SMTP bawaan dengan DKIM/SPF/DMARC — tanpa Mailgun atau SES |
+| **Backup** | Terjadwal, database + volume, pemulihan satu klik, ekspor kapan saja |
+| **Monitoring real-time** | Log build langsung, metrik container, geografi pengunjung, dan campuran respons per kode — [~1,4 µs per request, nol tulisan DB per request](docs/monitoring.md) |
+| **Skalabilitas** | Auto-scaling di cloud, siap multi-node di self-hosted |
+| **Portabilitas** | Container Docker standar — pindah antar penyedia dengan bebas |
+| **Docker Compose** | Deploy file compose yang ada apa adanya |
+
+---
+
+## Deploy Ke Mana Saja
+
+- **OpenShip Cloud** — terkelola, auto-scaling, zero setup
+- **VPS mana pun** — Hetzner, DigitalOcean, Linode, OVH, dan lainnya
+- **Server dedicated** — bare metal, colo, homelab
+- **Multi-server** — sebarkan workload ke berbagai mesin
+
+Antarmuka yang sama tidak peduli di mana Anda deploy.
+
+---
+
+## Status
+
+Inti production-ready, sedang aktif dikembangkan. Self-hosting **gratis** (tanpa tagihan).
+
+**Selanjutnya:** cluster multi-node, UI load balancing, networking privat, monitoring lanjutan, dan pipeline CI/CD visual.
+
+---
+
+## Berkontribusi
+
+Lihat [CONTRIBUTING.md](../../CONTRIBUTING.md).
+
+---
+
+## Keamanan
+
+Menemukan kerentanan? Kami menerima laporan Anda — silakan laporkan secara **privat**,
+jangan di issue, PR, atau diskusi publik.
+
+- **Laporkan di sini (dianjurkan):** [Laporkan kerentanan](https://github.com/oblien/openship/security/advisories/new) — advisory GitHub privat, hanya terlihat oleh Anda dan maintainer.
+- Cakupan, informasi yang perlu disertakan, dan proses respons/pengungkapan kami: [SECURITY.md](../../SECURITY.md).
+
+Penelitian keamanan dengan niat baik **diizinkan** di bawah
+[kebijakan safe-harbor](../../SECURITY.md#safe-harbor) kami, dan kami dengan senang hati memberikan pengakuan
+untuk laporan pertama yang valid.
+
+---
+
+## Lisensi
+
+OpenShip adalah perangkat lunak **open-source**, berlisensi di bawah [Apache License 2.0](../../LICENSE).
+
+Anda dapat menggunakan, menjalankan, memodifikasi, self-host, dan mendistribusikannya — termasuk dalam
+produk komersial dan closed-source — di bawah ketentuan lisensi Apache 2.0. Lihat
+[LICENSE](../../LICENSE) untuk teks lengkap.