Document i18n requirements for contributors:
- No hardcoded strings policy
- svelte-i18n interpolation format ({ values: {} })
- Translation key naming conventions
- Checklist for PRs with UI text
5.4 KiB
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>/<origin>/<branch_name>
type:feature|bugfix|hotfix|refactor|release|chore|docsorigin:internal: created/owned by maintainersexternal: branches/commits authored by third-party contributors (PRs)
Examples:
feature/internal/offline-cache-encryptionbugfix/internal/login-footer-alignmentdocs/internal/contributing-processfeature/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
main- Releases ONLY. Protected branch. Merging here triggers a tagged release.pre-release- Integration branch. All features and fixes merge here first.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)
- Triage
- Confirm scope and that it does not touch protected areas (audio routing/backends, credential storage, etc.) unless requested.
- Verify PR targets
pre-release(notmain).
- Check out the PR
gh pr checkout <PR_NUMBER>
- Rename the checked-out branch (local)
- Use an
externalbranch name so it's obvious these commits are third-party authored: git branch -m <type>/external/<topic>
- Use an
- Merge to pre-release
git checkout pre-releasegit merge --no-ff <type>/external/<topic>
- Run checks
- Frontend:
npm run build - Backend (when Rust changes):
cargo check(run fromsrc-tauri/)
- Frontend:
- Push pre-release
git push origin pre-release
- Close the PR with a comment explaining it was merged to
pre-release.
Releasing to main
When ready to release:
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
- Read the existing locale files first to check if a translation already exists
- Reuse existing keys - avoid duplicating translations
- 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:
<script lang="ts">
import { t } from '$lib/i18n';
</script>
<!-- In templates -->
<button>{$t('actions.save')}</button>
<span>{$t('settings.audio.title')}</span>
Interpolation Format
svelte-i18n requires a specific format for variable interpolation:
// ❌ 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:
{
"actions": {
"save": "Save",
"cancel": "Cancel",
"delete": "Delete"
},
"settings": {
"audio": {
"title": "Audio",
"streamingQuality": "Streaming Quality"
}
}
}
Common top-level sections:
actions- Buttons and common actionstoast- Toast notification messagessettings- Settings view labelslibrary- Local library related textplayer- Player controls and statuserrors- Error messagesempty- Empty state messages
Using Translations in Script Context
The $t() store can only be used in reactive contexts. In Svelte 5:
// ❌ 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