feat(i18n): add Persian (Farsi) localization with RTL support#9299
feat(i18n): add Persian (Farsi) localization with RTL support#9299danialshirali16 wants to merge 24 commits into
Conversation
Register Vazirmatn as a second font family for RTL content. In the backend node renderers, read the TipTap `dir` attribute on paragraph and heading nodes; when `dir === "rtl"`, apply fontFamily "Vazirmatn" and default textAlign to "right" so that Persian/Arabic characters are shaped correctly and flow right-to-left. Mirror the same Vazirmatn Font.register and a `[dir='rtl']` stylesheet rule in the frontend HTML-based export path. LTR (Inter) behaviour is completely unchanged. https://claude.ai/code/session_01BxEgURqex1hukdwboM3XF6
fix(pdf): fix RTL text rendering (Persian/Arabic) in Pages PDF export
Begins the full Persian localization of Plane. Translates all 814 keys in common.json into professional product-marketing Persian, following the project terminology conventions (تسک for work item, اسپرینت for cycle, فضای کاری for workspace). All JSON keys, ICU placeholders, and HTML syntax are preserved unchanged. https://claude.ai/code/session_01M7gF1AE6awDVuqXRZmPiTL
Translates all workspace settings UI strings including general settings, members, billing, exports, webhooks, API tokens, integrations, group syncing, identity, applications (OAuth/consent flows), and Plane AI. https://claude.ai/code/session_01M7gF1AE6awDVuqXRZmPiTL
Translates project settings including general, members, states, labels, estimates, automations, workflows, work item types, features (cycles, modules, views, pages, intake, time tracking, milestones), and auto-schedule cycle configuration. https://claude.ai/code/session_01M7gF1AE6awDVuqXRZmPiTL
Translates work item type settings including property types, attributes, formula builder, hierarchy configuration, enable/disable flows, and all validation/toast messages. https://claude.ai/code/session_01M7gF1AE6awDVuqXRZmPiTL
Translates all integration UI strings: GitHub, GitLab, GitLab Enterprise, Slack, Sentry, Bitbucket DC, OAuth Bridge, GitHub Enterprise, Silo error messages, and import status labels. https://claude.ai/code/session_01M7gF1AE6awDVuqXRZmPiTL
Translates work item CRUD, comments/replies, layouts, states, relations, archive/restore, bulk operations, recurring work items, and epics. https://claude.ai/code/session_01M7gF1AE6awDVuqXRZmPiTL
Translates project cycles, views, issues, modules, pages, disabled-project states, module statuses/layouts, member fields, and CSV import flows. https://claude.ai/code/session_01M7gF1AE6awDVuqXRZmPiTL
Translates workspace creation, dashboard, analytics, projects, views, draft issues, pages, cycles, and CSV member import flows. https://claude.ai/code/session_01M7gF1AE6awDVuqXRZmPiTL
Translates SSO domain management, SAML/OIDC providers, sign-in/sign-up, password management, forgot/reset password, and LDAP auth flows. https://claude.ai/code/session_01M7gF1AE6awDVuqXRZmPiTL
Translates project/work-item/page template settings, publish to marketplace form fields, empty states, toasts, and delete/unpublish confirmations. https://claude.ai/code/session_01M7gF1AE6awDVuqXRZmPiTL
Translates automation settings, triggers, conditions, actions, schedules, cron expressions, table columns, toasts, empty states, and global automations. https://claude.ai/code/session_01M7gF1AE6awDVuqXRZmPiTL
Translates common, project, workspace, and settings empty state messages covering work items, cycles, modules, views, pages, epics, analytics, and more. https://claude.ai/code/session_01M7gF1AE6awDVuqXRZmPiTL
Translates command palette actions: contextual, creation, navigation, account, miscellaneous, preferences, help actions, placeholders, and group titles. https://claude.ai/code/session_01M7gF1AE6awDVuqXRZmPiTL
Translates product tour steps for work items, cycles, modules, pages, intake, MCP connectors, navigation modal, get-started checklist and sections. https://claude.ai/code/session_01M7gF1AE6awDVuqXRZmPiTL
Translates account settings (profile, preferences, notifications, security, API tokens, activity, connections), profile stats/tabs/empty-states, themes. https://claude.ai/code/session_01M7gF1AE6awDVuqXRZmPiTL
Translates wiki collections (predefined, create/edit/delete modals, menus, list columns, toasts) and wiki upgrade flow with nested pages banner. https://claude.ai/code/session_01M7gF1AE6awDVuqXRZmPiTL
Translates page navigation pane (outline/info/assets/comments tabs, sticky toasts) and page actions (move page, remove from collection). https://claude.ai/code/session_01M7gF1AE6awDVuqXRZmPiTL
Translates workflow (69), inbox (64), home (59), update (48), notification (46), editor (46), stickies (43), accessibility (29), navigation (28), cycle (25), module (4) namespace files. https://claude.ai/code/session_01M7gF1AE6awDVuqXRZmPiTL
Adds 'fa' to TLanguage union type and SUPPORTED_LANGUAGES list so Persian appears in the language selector UI. https://claude.ai/code/session_01M7gF1AE6awDVuqXRZmPiTL
Add Persian (Farsi) language support
Set document.documentElement.dir based on the active language so RTL locales like Persian mirror the entire UI layout, not just the text. - Add RTL_LANGUAGES list and getLanguageDirection/isRTLLanguage helpers - setLanguage now sets the document dir on language change - TranslationProvider applies dir for the initial language on first load Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XvFYTPK6RXtYnNrL9xkGTX
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XvFYTPK6RXtYnNrL9xkGTX
|
|
📝 WalkthroughWalkthroughAdds Persian ( ChangesPersian Language and RTL Infrastructure
PDF RTL Rendering with Vazirmatn Font
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/live/src/lib/pdf/plane-pdf-exporter.tsx`:
- Around line 56-57: Replace process.cwd() with __dirname in the
vazirmatnFontDir path resolution to ensure the font directory is resolved from
the module's location rather than the launch directory. This prevents font
loading failures when the service is started from different working directories.
Change the path.resolve call to use __dirname as the base path instead of
process.cwd().
In `@packages/i18n/src/locales/fa/common.json`:
- Line 259: The "workspace_name" field in the Persian locale file (common.json)
currently uses hyphens to connect Persian words (نام-فضای-کاری), which causes
them to render as a single hyphenated token in the UI instead of separate words.
Replace the hyphenated Persian phrase with a properly spaced version using
standard spaces between the words instead of hyphens.
In `@packages/i18n/src/locales/fa/home.json`:
- Line 41: In the Persian translation file home.json, replace the value "اخیرها"
with "موارد اخیر" for the title field that labels the recents section. This
change uses the conventional Persian UI label that reads more naturally to users
instead of the current awkward phrasing.
In `@packages/i18n/src/locales/fa/notification.json`:
- Around line 35-38: The "mentions" object in the notification.json file
currently contains copy that refers to assigned tasks rather than mentions.
Update both the "title" and "description" properties within the "mentions"
section to use mention-specific wording that accurately reflects the purpose of
the mentions tab, rather than reusing the assigned-task language. Ensure the new
text clearly indicates that no mentions have been received or that mentions will
appear in this location.
In `@packages/i18n/src/locales/fa/project.json`:
- Around line 169-183: The Persian translation file uses the term "صفحه" (page)
throughout the public, archived, and shared sections, but this configuration is
for saved views, so the terminology should be "نما" (view) instead. Replace all
instances of "صفحه" with "نما" and "صفحات" with "نماها" in the titles and
descriptions of the public, archived, and shared objects to maintain consistent
view terminology and avoid confusing users about the purpose of these sections.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e7eadf4a-2a85-4cde-81fa-b99b69f80190
📒 Files selected for processing (37)
apps/live/src/lib/pdf/node-renderers.tsxapps/live/src/lib/pdf/plane-pdf-exporter.tsxapps/web/core/components/editor/pdf/document.tsxapps/web/core/constants/editor.tspackages/i18n/src/constants/language.tspackages/i18n/src/core/set-language.tspackages/i18n/src/index.tspackages/i18n/src/locales/fa/accessibility.jsonpackages/i18n/src/locales/fa/auth.jsonpackages/i18n/src/locales/fa/automation.jsonpackages/i18n/src/locales/fa/common.jsonpackages/i18n/src/locales/fa/cycle.jsonpackages/i18n/src/locales/fa/editor.jsonpackages/i18n/src/locales/fa/empty-state.jsonpackages/i18n/src/locales/fa/home.jsonpackages/i18n/src/locales/fa/inbox.jsonpackages/i18n/src/locales/fa/integration.jsonpackages/i18n/src/locales/fa/module.jsonpackages/i18n/src/locales/fa/navigation.jsonpackages/i18n/src/locales/fa/notification.jsonpackages/i18n/src/locales/fa/page.jsonpackages/i18n/src/locales/fa/power-k.jsonpackages/i18n/src/locales/fa/project-settings.jsonpackages/i18n/src/locales/fa/project.jsonpackages/i18n/src/locales/fa/settings.jsonpackages/i18n/src/locales/fa/stickies.jsonpackages/i18n/src/locales/fa/template.jsonpackages/i18n/src/locales/fa/tour.jsonpackages/i18n/src/locales/fa/update.jsonpackages/i18n/src/locales/fa/wiki.jsonpackages/i18n/src/locales/fa/work-item-type.jsonpackages/i18n/src/locales/fa/work-item.jsonpackages/i18n/src/locales/fa/workflow.jsonpackages/i18n/src/locales/fa/workspace-settings.jsonpackages/i18n/src/locales/fa/workspace.jsonpackages/i18n/src/provider/index.tsxpackages/i18n/src/types/language.ts
| const vazirmatnFontDir = path.resolve(process.cwd(), "fonts/vazirmatn"); | ||
|
|
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Avoid process.cwd() for font asset resolution in the live exporter.
This path is launch-directory dependent; running the service from a different working directory can break RTL font loading and PDF rendering. Resolve from the module location instead.
Suggested fix
+import { fileURLToPath } from "node:url";
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
-const vazirmatnFontDir = path.resolve(process.cwd(), "fonts/vazirmatn");
+const vazirmatnFontDir = path.resolve(__dirname, "../../../fonts/vazirmatn");🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/live/src/lib/pdf/plane-pdf-exporter.tsx` around lines 56 - 57, Replace
process.cwd() with __dirname in the vazirmatnFontDir path resolution to ensure
the font directory is resolved from the module's location rather than the launch
directory. This prevents font loading failures when the service is started from
different working directories. Change the path.resolve call to use __dirname as
the base path instead of process.cwd().
| "no_pending_invites": "دعوتنامه در انتظاری وجود ندارد", | ||
| "you_can_see_here_if_someone_invites_you_to_a_workspace": "اگر کسی شما را به فضای کاری دعوت کند اینجا میبینید", | ||
| "back_to_home": "بازگشت به صفحه اصلی", | ||
| "workspace_name": "نام-فضای-کاری", |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Use a spaced Persian phrase here.
نام-فضای-کاری will render as a hyphenated token in the UI. Use a normal Persian phrase instead.
Suggested fix
- "workspace_name": "نام-فضای-کاری",
+ "workspace_name": "نام فضای کاری",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "workspace_name": "نام-فضای-کاری", | |
| "workspace_name": "نام فضای کاری", |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/i18n/src/locales/fa/common.json` at line 259, The "workspace_name"
field in the Persian locale file (common.json) currently uses hyphens to connect
Persian words (نام-فضای-کاری), which causes them to render as a single
hyphenated token in the UI instead of separate words. Replace the hyphenated
Persian phrase with a properly spaced version using standard spaces between the
words instead of hyphens.
| "title_plural": "لینکهای سریع" | ||
| }, | ||
| "recents": { | ||
| "title": "اخیرها", |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Use a natural label for the recents section.
اخیرها reads awkwardly in Persian UI copy; موارد اخیر is the conventional label.
Suggested fix
- "title": "اخیرها",
+ "title": "موارد اخیر",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "title": "اخیرها", | |
| "title": "موارد اخیر", |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/i18n/src/locales/fa/home.json` at line 41, In the Persian
translation file home.json, replace the value "اخیرها" with "موارد اخیر" for the
title field that labels the recents section. This change uses the conventional
Persian UI label that reads more naturally to users instead of the current
awkward phrasing.
| "mentions": { | ||
| "title": "هیچ تسکی واگذار نشده است", | ||
| "description": "بهروزرسانیهای تسکهای واگذارشده به شما را میتوانید\n اینجا مشاهده کنید" | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Make the mentions empty state mention-specific.
This block is still reusing the assigned-task copy, so the mentions tab never shows wording that actually refers to mentions.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/i18n/src/locales/fa/notification.json` around lines 35 - 38, The
"mentions" object in the notification.json file currently contains copy that
refers to assigned tasks rather than mentions. Update both the "title" and
"description" properties within the "mentions" section to use mention-specific
wording that accurately reflects the purpose of the mentions tab, rather than
reusing the assigned-task language. Ensure the new text clearly indicates that
no mentions have been received or that mentions will appear in this location.
| "public": { | ||
| "title": "هنوز صفحه عمومیای وجود ندارد", | ||
| "description": "صفحاتی که با همه اعضای پروژه به اشتراک گذاشته شدهاند را اینجا ببینید.", | ||
| "primary_button": { | ||
| "text": "اولین صفحه را ایجاد کنید" | ||
| } | ||
| }, | ||
| "archived": { | ||
| "title": "هنوز صفحه بایگانیشدهای وجود ندارد", | ||
| "description": "صفحاتی که در دیدرس نیستند را بایگانی کنید. در صورت نیاز آنها را اینجا دسترسی داشته باشید." | ||
| }, | ||
| "shared": { | ||
| "title": "هنوز صفحه به اشتراک گذاشتهشدهای وجود ندارد", | ||
| "description": "صفحاتی که دیگران با شما به اشتراک گذاشتهاند اینجا نمایش داده میشوند." | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Use view terminology in project_views.
This section is for saved views, so صفحه is misleading here; keep it as نما to match the section and avoid confusing users.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/i18n/src/locales/fa/project.json` around lines 169 - 183, The
Persian translation file uses the term "صفحه" (page) throughout the public,
archived, and shared sections, but this configuration is for saved views, so the
terminology should be "نما" (view) instead. Replace all instances of "صفحه" with
"نما" and "صفحات" with "نماها" in the titles and descriptions of the public,
archived, and shared objects to maintain consistent view terminology and avoid
confusing users about the purpose of these sections.
Description
Adds Persian (Farsi) localization to Plane with full right-to-left (RTL) UI support.
Localization
falocale: 28 translation files underpackages/i18n/src/locales/fa/(3,837 keys, 100% parity with English — verified viacheck:sync).TLanguage(types/language.ts) andSUPPORTED_LANGUAGES(constants/language.ts) as{ label: "فارسی", value: "fa" }.RTL support (UI)
RTL_LANGUAGESlist plusisRTLLanguage()/getLanguageDirection()helpers, exported from the package entry point.setLanguage()now setsdocument.documentElement.dir(rtl/ltr) on every language change, so the entire layout mirrors — not just the text.TranslationProviderapplies the correctdirfor the initial language on first load, so a stored Persian preference renders RTL immediately rather than only after a manual switch.RTL support (PDF export)
apps/live/src/lib/pdf/) detect per-node content direction (dirattr), default RTL paragraphs/headings to right alignment, and apply the Vazirmatn font for correct Persian shaping. Font path resolved viaimport.meta.urlfor deterministic, module-relative loading.Type of Change
Screenshots and Media (if applicable)
Test Scenarios
pnpm --filter @plane/i18n check:types→ passes (0 errors).pnpm --filter @plane/i18n check:lint→ 0 errors.pnpm --filter @plane/i18n check:sync→ all locales 100% in sync;faat 3,837/3,837 keys.document.documentElement.dirtortland mirrors the UI layout; switching back to an LTR language restoresltr.References
Summary by CodeRabbit
Release Notes