# Contributing to QBZ This project is actively evolving. Contributions are welcome, but we have a few rules to keep releases stable and avoid regressions (especially around audio output). ## Quick rules - Write clear, concise English (no emojis in code, comments, or commit messages). - Keep PRs focused and small when possible. - Do not change app branding or legal disclaimers without discussing it first. - Do not modify protected audio-backend behavior unless explicitly requested by the maintainer. ## Branch naming We use a consistent branch naming scheme: `//` - `type`: `feature` | `bugfix` | `hotfix` | `refactor` | `release` | `chore` | `docs` - `origin`: - `internal`: created/owned by maintainers - `external`: branches/commits authored by third-party contributors (PRs) Examples: - `feature/internal/offline-cache-encryption` - `bugfix/internal/login-footer-alignment` - `docs/internal/contributing-process` - `feature/external/add-album-to-playlist` ## Branch workflow We use a **pre-release integration branch** to keep `main` stable and release-ready at all times. ``` feature/xyz ──┐ bugfix/abc ──┼──> pre-release ──> main (tagged release) hotfix/123 ──┘ ``` ### Branch hierarchy 1. **`main`** - Releases ONLY. Protected branch. Merging here triggers a tagged release. 2. **`pre-release`** - Integration branch. All features and fixes merge here first. 3. **`feature/*`, `bugfix/*`, etc.** - Individual work branches. ### For contributors **All PRs must target `pre-release`, not `main`.** PRs targeting `main` will be closed and asked to retarget to `pre-release`. ### Procedure (maintainer) 1. **Triage** - Confirm scope and that it does not touch protected areas (audio routing/backends, credential storage, etc.) unless requested. - Verify PR targets `pre-release` (not `main`). 2. **Check out the PR** - `gh pr checkout ` 3. **Rename the checked-out branch (local)** - Use an `external` branch name so it's obvious these commits are third-party authored: - `git branch -m /external/` 4. **Merge to pre-release** - `git checkout pre-release` - `git merge --no-ff /external/` 5. **Run checks** - Frontend: `npm run build` - Backend (when Rust changes): `cargo check` (run from `src-tauri/`) 6. **Push pre-release** - `git push origin pre-release` 7. **Close the PR with a comment** explaining it was merged to `pre-release`. ### Releasing to main When ready to release: ```bash git checkout main git merge pre-release git push origin main git tag vX.Y.Z git push origin vX.Y.Z ``` This is done exclusively by maintainers. ### Merge strategy note (to preserve “external” authorship) If you want the git history to clearly show third-party authored commits, avoid “squash merge”. Prefer: - **Create a merge commit**, or - **Rebase and merge** (preserves individual commits/authors) ## What to include in PRs - A short description of the problem and solution. - Screenshots for UI changes when possible. - Notes about any breaking changes or migrations. ## What not to include - Large refactors mixed with feature work. - Changes that reintroduce removed UI/UX patterns (for example, exporting offline cache files). --- ## Internationalization (i18n) **All UI text must use the translation system.** No hardcoded strings in Svelte templates. ### Locale Files Translations are stored in: - `src/lib/i18n/locales/en.json` (English) - `src/lib/i18n/locales/es.json` (Spanish) ### Before Adding New Text 1. **Read the existing locale files first** to check if a translation already exists 2. **Reuse existing keys** - avoid duplicating translations 3. **Add to both files** - every new key must exist in en.json AND es.json ### How to Use Translations Import and use the `t` store: ```svelte {$t('settings.audio.title')} ``` ### Interpolation Format svelte-i18n requires a specific format for variable interpolation: ```typescript // ❌ WRONG - will show {name} literally $t('greeting', { name: userName }) // ✅ CORRECT - wrap in values object $t('greeting', { values: { name: userName } }) ``` ### Translation Key Naming Use dot notation for namespacing: ```json { "actions": { "save": "Save", "cancel": "Cancel", "delete": "Delete" }, "settings": { "audio": { "title": "Audio", "streamingQuality": "Streaming Quality" } } } ``` Common top-level sections: - `actions` - Buttons and common actions - `toast` - Toast notification messages - `settings` - Settings view labels - `library` - Local library related text - `player` - Player controls and status - `errors` - Error messages - `empty` - Empty state messages ### Using Translations in Script Context The `$t()` store can only be used in reactive contexts. In Svelte 5: ```typescript // ❌ WRONG - breaks Svelte preprocessing const label = $t('some.key'); // ✅ CORRECT - use $derived const label = $derived($t('some.key')); ``` ### Checklist for PRs with UI Text - [ ] No hardcoded strings in Svelte templates - [ ] New translation keys added to both en.json and es.json - [ ] Variable interpolation uses `{ values: { ... } }` format - [ ] Checked existing keys before creating new ones (no duplicates) - [ ] Keys follow naming conventions