Thanks for your interest in contributing. This document covers the local development workflow, the checks CI runs, and the code style we expect.
The runtime stack is GTK4 + libadwaita + PyGObject. Install the system
libraries before running uv sync.
sudo apt install libcairo2-dev libgirepository-2.0-dev libgtk-4-dev libadwaita-1-devOn Fedora / Arch / openSUSE, install the equivalent gtk4, libadwaita,
gobject-introspection, and cairo development packages.
brew install gtk4 libadwaita gobject-introspection pygobject3Install MSYS2 and from the UCRT64 shell:
pacman -S mingw-w64-ucrt-x86_64-gtk4 \
mingw-w64-ucrt-x86_64-libadwaita \
mingw-w64-ucrt-x86_64-python \
mingw-w64-ucrt-x86_64-python-gobject \
mingw-w64-ucrt-x86_64-python-pipAll uv commands below must be run from the UCRT64 shell.
git clone https://github.com/estudio-hawara/salesforce-object-flow.git
cd salesforce-object-flow
uv sync
uv run salesforce-object-flowCI runs these four commands. Run them locally before opening a pull request.
uv run ruff check salesforce_object_flow/ tests/
uv run ruff format --check salesforce_object_flow/ tests/
uv run pyright salesforce_object_flow/ tests/
uv run pytest tests/ -vAuto-fix the formatter / lint findings with:
uv run ruff format salesforce_object_flow/ tests/
uv run ruff check --fix salesforce_object_flow/ tests/- Formatter / linter:
ruffwithselect = ["E", "F", "W", "I"],line-length = 100,target-version = "py312". - Type checker:
pyrightin strict mode for the package. - Type narrowing: don't use
assertfor it — preferisinstancechecks, early returns, orcastwhen the runtime invariant is clear. - Comments: write one only when the why is non-obvious. Don't explain what the code does — naming and types should carry that.
- Async / threads: use
GLib.idle_addand theTimerhelper for deferred work. Noasyncio, no manual threads in v1. - Logging: one logger per module via
import logging; log = logging.getLogger(__name__).
The app uses Python's gettext at runtime and babel (pybabel) at
development time. The source language is English; translations live in
po/<lang>.po.
-
Marking strings:
- Inside functions / methods: wrap with
_(). - At module or class scope (constants,
ClassVar,Enumvalues): wrap withN_()so extraction picks them up, then call_()at the use-site. Thetests/test_i18n.pyregression guard fails the build if a_()call sneaks into module / class scope. - For runtime values, prefer
_("Hello {name}").format(name=...)over f-strings so themsgidstays stable.
- Inside functions / methods: wrap with
-
Workflow:
uv run python scripts/i18n.py extract # source/.py → po/salesforce-object-flow.pot uv run python scripts/i18n.py update # merge .pot into each po/<lang>.po uv run python scripts/i18n.py compile # po/<lang>.po → locale/<lang>/LC_MESSAGES/<domain>.mo
-
Adding a new language: append the locale code (e.g.
fr,de_DE) topo/LINGUAS, runextractthenupdate— pybabel will createpo/<lang>.popopulated with the emptymsgstr ""skeleton. Translate andcompile. A first run can also usepybabel init -l <lang>if you prefer to seed the file manually. -
Verifying locally:
LANGUAGE=es uv run salesforce-object-flow(or any other locale code that has a compiled catalog underlocale/). -
Service errors: errors carry an
ErrorCodeand are translated at the toast boundary byi18n_errors.format_error. To add a new user-facing error, add a value toErrorCode, register a formatter ini18n_errors._TEMPLATES, and passcode=ErrorCode.X+params={…}on theraise. The English message you pass positionally still surfaces in logs and bug reports.
The locale/ directory is git-ignored — .mo artifacts are regenerated
from .po (the source of truth). Translators only need to touch
po/<lang>.po.
Version 1 focuses on the Composite REST API: building, validating, and submitting one transactional multi-object create. Other Salesforce APIs (Bulk, Streaming, Metadata) and broader features (org migrations, deploy flows) are out of scope until v1 ships.
For larger changes, please open an issue first to discuss the approach.