Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
<p align="center">
<a href="README.md"><img src="https://img.shields.io/badge/lang-English-0b7285" alt="English" /></a>
<a href="docs/i18n/README.ar.md"><img src="https://img.shields.io/badge/lang-العربية-555" alt="العربية" /></a>
<a href="docs/i18n/README.fa.md"><img src="https://img.shields.io/badge/lang-فارسی-555" alt="فارسی" /></a>
<a href="docs/i18n/README.zh.md"><img src="https://img.shields.io/badge/lang-简体中文-555" alt="简体中文" /></a>
<a href="docs/i18n/README.es.md"><img src="https://img.shields.io/badge/lang-Español-555" alt="Español" /></a>
<a href="docs/i18n/README.fr.md"><img src="https://img.shields.io/badge/lang-Français-555" alt="Français" /></a>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

/**
* Settings → General → Language. Switches the interface locale via the shared
* I18nProvider (which flips <html lang/dir>, so RTL for Arabic is automatic).
* I18nProvider (which flips <html lang/dir>, so RTL for Arabic and Persian is automatic).
* Each language is a selectable card showing its AUTONYM — its own name, in its
* own script (中文, Español, …), never translated into the current UI language.
*/
Expand All @@ -16,6 +16,7 @@ import { SettingsSection } from "./SettingsSection";
const NATIVE: Record<Locale, string> = {
en: "English",
ar: "العربية",
fa: "فارسی",
es: "Español",
fr: "Français",
de: "Deutsch",
Expand All @@ -29,6 +30,7 @@ const NATIVE: Record<Locale, string> = {
const CODE: Record<Locale, string> = {
en: "EN",
ar: "ع",
fa: "فا",
es: "ES",
fr: "FR",
de: "DE",
Expand Down
4 changes: 2 additions & 2 deletions apps/dashboard/src/app/(onboarding)/onboarding/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ const MoonIcon = () => (

/** Each language's own name / short glyph, in its own script (never translated). */
const LANG_NATIVE: Record<Locale, string> = {
en: "English", ar: "العربية", es: "Español", fr: "Français",
en: "English", ar: "العربية", fa: "فارسی", es: "Español", fr: "Français",
de: "Deutsch", pt: "Português", ja: "日本語", zh: "中文",
tr: "Türkçe",
};
const LANG_CODE: Record<Locale, string> = {
en: "EN", ar: "ع", es: "ES", fr: "FR", de: "DE", pt: "PT", ja: "日", zh: "中",
en: "EN", ar: "ع", fa: "فا", es: "ES", fr: "FR", de: "DE", pt: "PT", ja: "日", zh: "中",
tr: "TR",
};

Expand Down
30 changes: 17 additions & 13 deletions apps/dashboard/src/i18n/i18n-parity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ describe("i18n locale parity vs the English source", () => {
/**
* The check the two above CANNOT make.
*
* Both of them reason about key PRESENCE. A key that exists in all nine locales
* Both of them reason about key PRESENCE. A key that exists in all ten locales
* while still holding the English sentence is invisible to them — the locale is
* "complete", the suite is green, and the UI renders English. That is not
* hypothetical: `verifyEmail.code*` sat like that in 7 of 8 locales, and when the
Expand All @@ -266,26 +266,30 @@ describe("i18n locale parity vs the English source", () => {
* A namespace absent from this map must stay at zero.
*/
const UNTRANSLATED_BASELINE: Record<string, number> = {
deploy: 133,
settings: 99,
projectSettings: 48,
importProject: 43,
// +N on several rows: `fa` keeps product names and machine placeholders
// identical to English (Openship Cloud, gh CLI, AWS S3, PEM headers, `npm start`,
// `{name}: {label}`), matching ar/tr. Those strings are multi-word so they
// count here; they are not leftover UI copy.
deploy: 139,
settings: 114,
projectSettings: 54,
importProject: 45,
billing: 28,
onboarding: 24,
projectDetail: 19,
library: 18,
misc: 18,
onboarding: 27,
projectDetail: 21,
library: 19,
misc: 23,
projects: 17,
deployments: 14,
deployments: 16,
emailsAdmin: 12,
overview: 12,
chrome: 10,
chrome: 11,
widgets: 9,
dashboard: 6,
emails: 6,
servers: 6,
servers: 8,
migration: 3,
jobs: 2,
jobs: 3,
// `auth` is deliberately absent, i.e. pinned at 0: it was brought to zero when
// password reset moved from a link to a code, and it is the flow where an
// English-rendering string is most costly — somebody locked out of their account
Expand Down
4 changes: 2 additions & 2 deletions apps/dashboard/src/i18n/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,13 @@ import autoDns from "./locales/en/autoDns.json";
export const baseDictionary = { brand, auth, dashboard, settings, servers, billing, library, onboarding, deploy, deployments, importProject, projects, projectSettings, projectDetail, emails, emailsAdmin, chrome, overview, widgets, misc, migration, jobs, issues, autoDns };
export type Dictionary = typeof baseDictionary;

export const locales = ["en", "ar", "es", "fr", "de", "pt", "ja", "zh", "tr"] as const;
export const locales = ["en", "ar", "fa", "es", "fr", "de", "pt", "ja", "zh", "tr"] as const;
export type Locale = (typeof locales)[number];
export const defaultLocale: Locale = "en";
export const LOCALE_COOKIE = "openship-locale";

/** RTL languages. */
const rtlLocales = new Set<Locale>(["ar"]);
const rtlLocales = new Set<Locale>(["ar", "fa"]);
export function isRtl(locale: Locale): boolean {
return rtlLocales.has(locale);
}
Expand Down
111 changes: 111 additions & 0 deletions apps/dashboard/src/i18n/locales/fa/auth.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
{
"login": {
"title": "خوش آمدید",
"subtitle": "برای ادامه وارد حساب خود شوید.",
"emailLabel": "ایمیل",
"emailPlaceholder": "you@example.com",
"passwordLabel": "رمز عبور",
"passwordPlaceholder": "••••••••",
"forgot": "فراموش کردید؟",
"submit": "ورود",
"submitting": "در حال ورود…",
"noAccount": "حساب ندارید؟",
"createOne": "یکی بسازید"
},
"zeroAuth": {
"title": "ورود در اینجا در دسترس نیست",
"remoteBrowser": "این نمونه بدون حساب کاربری اجرا می‌شود و نشست فقط برای مرورگر روی همان ماشینی ساخته می‌شود که Openship روی آن اجراست. داشبورد را از {origin} باز کرده‌اید، بنابراین چیزی برای ورود وجود ندارد.",
"refused": "این نمونه بدون حساب کاربری اجرا می‌شود و سرور از ساخت نشست برای این مرورگر خودداری کرد؛ بنابراین ورود در اینجا کامل نمی‌شود.",
"remedy": "برای ورود با حساب، این را به محیط Openship اضافه کنید و آن را دوباره راه‌اندازی کنید:",
"remedyAdmin": "سپس با حساب مدیر ساخته‌شده هنگام نصب وارد شوید.",
"retry": "تلاش دوباره"
},
"register": {
"title": "حساب بسازید",
"subtitle": "در چند ثانیه با Openship شروع کنید.",
"nameLabel": "نام",
"namePlaceholder": "نام شما",
"emailLabel": "ایمیل",
"emailPlaceholder": "you@example.com",
"passwordLabel": "رمز عبور",
"passwordPlaceholder": "حداقل ۸ نویسه",
"submit": "ساخت حساب",
"submitting": "در حال ساخت حساب…",
"hasAccount": "از قبل حساب دارید؟",
"signIn": "ورود"
},
"forgotPassword": {
"title": "بازنشانی رمز عبور",
"subtitle": "ایمیل خود را وارد کنید تا یک کد بازنشانی برایتان بفرستیم.",
"emailLabel": "ایمیل",
"emailPlaceholder": "you@example.com",
"submit": "ارسال کد بازنشانی",
"submitting": "در حال ارسال…",
"backToSignIn": "بازگشت به ورود",
"spamHint": "نمی‌بینید؟ پوشه هرزنامه یا هرزنامه‌ها را بررسی کنید."
},
"resetPassword": {
"title": "رمز عبور تازه بگذارید",
"subtitle": "یک رمز قوی برای حساب خود انتخاب کنید.",
"codeSentTo": "یک کد ۶ رقمی به {email} ایمیل کردیم. آن را همراه رمز تازه در زیر وارد کنید.",
"codeSentGeneric": "کد بازنشانی را که ایمیل کردیم همراه رمز تازه وارد کنید.",
"passwordLabel": "رمز عبور تازه",
"passwordPlaceholder": "حداقل ۸ نویسه",
"confirmLabel": "تأیید رمز عبور",
"confirmPlaceholder": "رمز را دوباره وارد کنید",
"submit": "بازنشانی رمز عبور",
"submitting": "در حال بازنشانی…",
"backToSignIn": "بازگشت به ورود",
"doneTitle": "رمز بازنشانی شد",
"doneDescription": "رمز عبور شما با موفقیت به‌روز شد.",
"doneAction": "ورود"
},
"verifyEmail": {
"verifyingTitle": "در حال تأیید ایمیل شما…",
"verifyingSubtitle": "یک لحظه.",
"verifiedTitle": "ایمیل تأیید شد",
"verifiedDescription": "ایمیل شما تأیید شد. اکنون می‌توانید وارد شوید.",
"verifiedAction": "ادامه برای ورود",
"errorTitle": "تأیید ناموفق بود",
"errorDescription": "ممکن است کد نادرست یا منقضی شده باشد.",
"resendEmail": "ارسال دوباره کد",
"signIn": "ورود",
"pendingTitle": "ایمیل خود را تأیید کنید",
"pendingSentTo": "یک کد ۶ رقمی به {email} ایمیل کردیم. آن را در زیر وارد کنید تا ایمیل تأیید شود.",
"pendingGeneric": "کد تأییدی را که ایمیل کردیم وارد کنید.",
"resendVerification": "ارسال دوباره کد",
"backToSignIn": "بازگشت به ورود",
"codeLabel": "کد تأیید",
"codePlaceholder": "کد ۶ رقمی را وارد کنید",
"verifyCode": "تأیید ایمیل",
"codeLocked": "تلاش‌های زیاد — این کد قفل شد. کد تازه‌ای بخواهید و ایمیل را بررسی کنید.",
"codeExpired": "این کد منقضی شده است. یک کد تازه بخواهید.",
"codeInvalid": "کد نادرست است. دوباره بررسی کنید.",
"resendRateLimited": "لطفاً کمی صبر کنید و بعد کد دیگری بخواهید.",
"spamHint": "کد را نمی‌بینید؟ پوشه هرزنامه را بررسی کنید."
},
"oauth": {
"or": "یا",
"github": "ادامه با GitHub",
"google": "ادامه با Google"
},
"errors": {
"invalidCredentials": "ایمیل یا رمز عبور نادرست است.",
"serverUnreachable": "به سرور نرسیدیم. آیا API در حال اجراست؟",
"generic": "مشکلی پیش آمد. دوباره تلاش کنید.",
"oauthFailed": "ورود OAuth ناموفق بود. دوباره تلاش کنید.",
"passwordMin": "رمز عبور باید حداقل ۸ نویسه باشد.",
"passwordMismatch": "رمزها یکسان نیستند.",
"invalidToken": "کد بازنشانی نامعتبر یا موجود نیست. کد تازه‌ای بخواهید.",
"resetFailed": "بازنشانی رمز ممکن نشد.",
"createFailed": "ساخت حساب ممکن نشد.",
"verificationFailed": "تأیید ناموفق بود.",
"verificationExpired": "تأیید ناموفق بود. ممکن است کد منقضی شده باشد.",
"resendFailed": "ارسال دوباره ناموفق بود. دوباره تلاش کنید.",
"verificationSent": "ایمیل تأیید ارسال شد!"
},
"back": "بازگشت",
"toggleTheme": "تغییر پوسته",
"showPassword": "نمایش رمز عبور",
"hidePassword": "پنهان کردن رمز عبور"
}
29 changes: 29 additions & 0 deletions apps/dashboard/src/i18n/locales/fa/autoDns.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"checking": "در حال بررسی ارائه‌دهنده DNS شما…",
"noProviderTitle": "این رکوردها را به‌صورت خودکار تنظیم کنید",
"noProviderDesc": "یک ارائه‌دهنده DNS متصل کنید تا Openship این رکوردها را برایتان اضافه کند.",
"connect": "اتصال ارائه‌دهنده DNS",
"unauthorizedTitle": "ارائه‌دهنده DNS نیاز به اتصال دوباره دارد",
"unauthorizedDesc": "توکن ذخیره‌شده رد شد. برای پیکربندی خودکار DNS دوباره متصلش کنید.",
"reconnect": "اتصال دوباره ارائه‌دهنده",
"unavailableTitle": "به ارائه‌دهنده DNS نرسیدیم",
"unavailableDesc": "الان نتوانستیم زون شما را بررسی کنیم. کمی بعد دوباره تلاش کنید.",
"retry": "تلاش دوباره",
"managedBy": "{provider} مدیریت {zone} را بر عهده دارد",
"apply": "پیکربندی خودکار DNS",
"applying": "در حال پیکربندی…",
"reapply": "اعمال دوباره رکوردها",
"allInPlace": "همه رکوردها در جای خود هستند",
"actionCreate": "افزودن",
"actionUpdate": "به‌روزرسانی",
"actionAdopt": "تغییر اشاره",
"actionInSync": "در جای خود",
"actionConflict": "تداخل",
"conflictNote": "توسط Openship مدیریت نمی‌شود — آن را حذف کنید یا این رکورد را دستی تنظیم کنید.",
"outcomeApplied": "اعمال شد",
"outcomeSkipped": "رد شد",
"outcomeFailed": "ناموفق",
"resultDone": "DNS به‌روز شد.",
"resultPartial": "برخی رکوردها نیاز به توجه دارند.",
"applyFailed": "پیکربندی DNS ممکن نشد."
}
Loading