This page expands on CONTRIBUTING.md with more detail on specific workflows.
# Fork on GitHub first, then:
git clone https://github.com/YOUR_USERNAME/qtranslate.git
cd qtranslate
git remote add upstream https://github.com/ahatem/qtranslate.gitBefore starting any new work, sync with upstream:
git fetch upstream
git checkout develop
git merge upstream/develop
git push origin develop./gradlew :app:run -DappData="C:/Users/you/QTranslateTestData"Use a dedicated test data directory — separate from any real installation — so you can install test plugins and mess with settings freely.
# 1. Branch off develop — always
git checkout develop
git pull upstream develop
git checkout -b feature/my-feature-name
# 2. Make your changes, commit often
git add . # stage everything
git commit -m "feat(core): add X"
# 3. Keep your branch up to date if develop moves on
git fetch upstream
git rebase upstream/develop
# 4. Push and open a PR
git push origin feature/my-feature-nameOpen the PR against develop, not main.
We use Conventional Commits. This lets us generate changelogs automatically.
<type>(<scope>): <summary>
[optional body — explain WHY, not WHAT]
[optional footer — Closes #123, Breaking-Change: ...]
Types: feat, fix, refactor, docs, test, chore, style
Scopes: core, api, ui, plugins, ci, build
Examples:
feat(ui): add dirty indicator to SettingsDialog title bar
fix(core): prevent double DataStore instance on startup
refactor(plugin): extract PluginInstaller from PluginManager
docs(wiki): add plugin submission guide
chore(deps): bump FlatLaf to 3.8
We don't enforce a strict formatter, but please follow these conventions:
- Kotlin idioms — use
when,let,run,fold,mapValuesetc. instead of verbose if/else chains - No raw exceptions for expected failures — return
Result<T, Error>instead - No business logic in UI components — dispatch an intent, let the store handle it
- Blocking I/O on
Dispatchers.IO— wrapFiles.*, JDBC, synchronous HTTP inwithContext(Dispatchers.IO) - KDoc on public API — everything in
:apiand:core's public surface needs KDoc
| Module | Can import | Cannot import |
|---|---|---|
:api |
stdlib, kotlinx-coroutines (for suspend), kotlin-result | :core, :ui-swing, Swing |
:core |
:api, kotlinx, DataStore, Ktor |
:ui-swing, Swing |
:ui-swing |
:api, :core, Swing, FlatLaf |
:app |
:app |
everything | — |
:plugins:* |
:api, :plugins:common |
:core, :ui-swing |
Plugins importing :core is the most common mistake — if you find yourself needing something from :core in a plugin, it probably belongs in :api instead.
./gradlew testCI runs the suite on every push and a failure blocks the PR.
Test stack:
- JUnit 5 for test structure
kotlinx-coroutines-testfor suspend functions and flowsktor-client-mockfor scripting HTTP responsesFakePluginContext, published as a test fixture from:plugins:common, for plugin tests
Where coverage is thin: the use cases still in core/main/domain/usecase, the Swing panels, and
both MVI stores. Translation and lookup use cases live under core/workspace/{translation,lookup}.
Good places to start.
How to contribute a test:
- Pick a class with no direct coverage — check
src/test/kotlinin the relevant module first - Open an issue:
test: add tests for <ClassName>— a maintainer will label itgood first issue - Add tests under
src/test/kotlinin the relevant module - Open a PR — even a single well-written test is a meaningful contribution
Useful resources:
For UI changes, attach before/after screenshots to the PR — we do not have automated UI tests.
If you want to add a completely new service category (e.g. a grammar checker) rather than a new plugin for an existing category:
- Add the interface to
:api— discuss in an issue first since this is a breaking change surface - Add the
ServiceRoleenum entry in:api, pointingcontractat that interface. Roles are derived from the interfaces a service implements, so there is no separate mapping to keep in step - Add the use case under
:core—core/workspace/<feature>/for a workspace feature, otherwisecore/main/domain/usecase/ - Add the intent and state fields to
MainStore - Add the UI surface in
:ui-swing
This is a significant change — open an issue and discuss before starting.