Skip to content

feat(i18n): add Persian (Farsi) localization with RTL support#9299

Open
danialshirali16 wants to merge 24 commits into
makeplane:previewfrom
danialshirali16:claude/wizardly-keller-3vefan
Open

feat(i18n): add Persian (Farsi) localization with RTL support#9299
danialshirali16 wants to merge 24 commits into
makeplane:previewfrom
danialshirali16:claude/wizardly-keller-3vefan

Conversation

@danialshirali16

@danialshirali16 danialshirali16 commented Jun 23, 2026

Copy link
Copy Markdown

Description

Adds Persian (Farsi) localization to Plane with full right-to-left (RTL) UI support.

Localization

  • New fa locale: 28 translation files under packages/i18n/src/locales/fa/ (3,837 keys, 100% parity with English — verified via check:sync).
  • Registered Persian in TLanguage (types/language.ts) and SUPPORTED_LANGUAGES (constants/language.ts) as { label: "فارسی", value: "fa" }.

RTL support (UI)

  • Added RTL_LANGUAGES list plus isRTLLanguage() / getLanguageDirection() helpers, exported from the package entry point.
  • setLanguage() now sets document.documentElement.dir (rtl/ltr) on every language change, so the entire layout mirrors — not just the text.
  • TranslationProvider applies the correct dir for 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)

  • PDF node renderers (apps/live/src/lib/pdf/) detect per-node content direction (dir attr), default RTL paragraphs/headings to right alignment, and apply the Vazirmatn font for correct Persian shaping. Font path resolved via import.meta.url for deterministic, module-relative loading.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • Feature (non-breaking change which adds functionality)
  • Improvement (change that would cause existing functionality to not work as expected)
  • Code refactoring
  • Performance improvements
  • Documentation update

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; fa at 3,837/3,837 keys.
  • Switching language to Persian flips document.documentElement.dir to rtl and mirrors the UI layout; switching back to an LTR language restores ltr.
  • Reloading the app with Persian stored as the preference renders RTL on first paint.
  • Exporting a page with Persian content to PDF renders right-aligned text with the Vazirmatn font.

References

Summary by CodeRabbit

Release Notes

  • New Features
    • Added Persian language support with complete application localization across all UI surfaces.
    • Implemented right-to-left (RTL) text direction support for proper rendering of RTL content in PDF exports and user interface.
    • Integrated Vazirmatn font to ensure optimal display of Persian text in PDFs and documents.

claude and others added 24 commits June 1, 2026 08:39
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
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
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ danialshirali16
❌ claude
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds Persian (fa) as a supported language with full RTL infrastructure: a new TLanguage union member, RTL_LANGUAGES/isRTLLanguage/getLanguageDirection helpers, document.dir wiring in setLanguage and TranslationProvider, and ~5,000 lines of Persian locale JSON files. Separately, registers the Vazirmatn font in both PDF exporters and updates paragraph/heading node renderers to apply RTL-aware font and text alignment.

Changes

Persian Language and RTL Infrastructure

Layer / File(s) Summary
RTL i18n infrastructure and language type
packages/i18n/src/types/language.ts, packages/i18n/src/constants/language.ts, packages/i18n/src/core/set-language.ts, packages/i18n/src/index.ts, packages/i18n/src/provider/index.tsx
TLanguage gains "fa", SUPPORTED_LANGUAGES adds the Persian entry, RTL_LANGUAGES/isRTLLanguage/getLanguageDirection are defined and exported, setLanguage now writes document.documentElement.dir, and TranslationProvider applies both lang and dir on init before marking ready.
Persian locale JSON files
packages/i18n/src/locales/fa/*
Full set of Persian translation files added: accessibility, auth, automation, common, cycle, editor, empty-state, home, inbox, integration, module, navigation, notification, page, power-k, project-settings, project, settings, stickies, template, tour, update, wiki, work-item-type, work-item, workflow, workspace-settings, and workspace.

PDF RTL Rendering with Vazirmatn Font

Layer / File(s) Summary
Vazirmatn font registration and editor RTL style
apps/live/src/lib/pdf/plane-pdf-exporter.tsx, apps/web/core/components/editor/pdf/document.tsx, apps/web/core/constants/editor.ts
Vazirmatn regular and bold .woff files are registered via Font.register in both the live and web PDF exporters; EDITOR_PDF_FONT_FAMILY_STYLES gains a [dir='rtl'] override setting fontFamily: Vazirmatn and textAlign: right.
RTL paragraph and heading node renderers
apps/live/src/lib/pdf/node-renderers.tsx
Adds getRtlStyle helper returning Vazirmatn font style when dir === "rtl"; updates nodeRenderers.paragraph and nodeRenderers.heading to compute effectiveTextAlign (defaults to right for RTL) and include rtlStyle in the Text style array.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • sriramveeraghanta
  • prateekshourya29

Poem

🐇 A new tongue arrives, right-to-left it flows,
فارسی now blooms where the PDF goes,
Vazirmatn glyphs dance across every line,
The rabbit registers fonts — how divine!
RTL or LTR, we render them all,
No language too distant to heed our call! 🌸

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly summarizes the main change: adding Persian localization with RTL support, which aligns directly with the extensive file changes and language infrastructure additions.
Description check ✅ Passed The PR description covers all template sections with comprehensive detail: describes the localization work, RTL UI/PDF support, and includes test scenarios demonstrating feature validation. All required template sections are completed.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6c9dbb5 and be86965.

📒 Files selected for processing (37)
  • apps/live/src/lib/pdf/node-renderers.tsx
  • apps/live/src/lib/pdf/plane-pdf-exporter.tsx
  • apps/web/core/components/editor/pdf/document.tsx
  • apps/web/core/constants/editor.ts
  • packages/i18n/src/constants/language.ts
  • packages/i18n/src/core/set-language.ts
  • packages/i18n/src/index.ts
  • packages/i18n/src/locales/fa/accessibility.json
  • packages/i18n/src/locales/fa/auth.json
  • packages/i18n/src/locales/fa/automation.json
  • packages/i18n/src/locales/fa/common.json
  • packages/i18n/src/locales/fa/cycle.json
  • packages/i18n/src/locales/fa/editor.json
  • packages/i18n/src/locales/fa/empty-state.json
  • packages/i18n/src/locales/fa/home.json
  • packages/i18n/src/locales/fa/inbox.json
  • packages/i18n/src/locales/fa/integration.json
  • packages/i18n/src/locales/fa/module.json
  • packages/i18n/src/locales/fa/navigation.json
  • packages/i18n/src/locales/fa/notification.json
  • packages/i18n/src/locales/fa/page.json
  • packages/i18n/src/locales/fa/power-k.json
  • packages/i18n/src/locales/fa/project-settings.json
  • packages/i18n/src/locales/fa/project.json
  • packages/i18n/src/locales/fa/settings.json
  • packages/i18n/src/locales/fa/stickies.json
  • packages/i18n/src/locales/fa/template.json
  • packages/i18n/src/locales/fa/tour.json
  • packages/i18n/src/locales/fa/update.json
  • packages/i18n/src/locales/fa/wiki.json
  • packages/i18n/src/locales/fa/work-item-type.json
  • packages/i18n/src/locales/fa/work-item.json
  • packages/i18n/src/locales/fa/workflow.json
  • packages/i18n/src/locales/fa/workspace-settings.json
  • packages/i18n/src/locales/fa/workspace.json
  • packages/i18n/src/provider/index.tsx
  • packages/i18n/src/types/language.ts

Comment on lines +56 to +57
const vazirmatnFontDir = path.resolve(process.cwd(), "fonts/vazirmatn");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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": "نام-فضای-کاری",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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.

Suggested change
"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": "اخیرها",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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.

Suggested change
"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.

Comment on lines +35 to +38
"mentions": {
"title": "هیچ تسکی واگذار نشده است",
"description": "به‌روزرسانی‌های تسک‌های واگذارشده به شما را می‌توانید\n اینجا مشاهده کنید"
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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.

Comment on lines +169 to +183
"public": {
"title": "هنوز صفحه عمومی‌ای وجود ندارد",
"description": "صفحاتی که با همه اعضای پروژه به اشتراک گذاشته شده‌اند را اینجا ببینید.",
"primary_button": {
"text": "اولین صفحه را ایجاد کنید"
}
},
"archived": {
"title": "هنوز صفحه بایگانی‌شده‌ای وجود ندارد",
"description": "صفحاتی که در دیدرس نیستند را بایگانی کنید. در صورت نیاز آن‌ها را اینجا دسترسی داشته باشید."
},
"shared": {
"title": "هنوز صفحه به اشتراک گذاشته‌شده‌ای وجود ندارد",
"description": "صفحاتی که دیگران با شما به اشتراک گذاشته‌اند اینجا نمایش داده می‌شوند."
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants