بنقرة واحدة
qtpass-localization
// QtPass localization workflow - translation files, updating, adding languages
// QtPass localization workflow - translation files, updating, adding languages
QtPass CI/CD workflow - run GitHub Actions locally with act, linters, formatters
QtPass localization audit - structural checks on .ts files (placeholders, HTML balance, mnemonics, mixed-script artifacts)
QtPass GitHub interaction - PRs, issues, branches, merging
Bug fixing workflow for QtPass - find, fix, test, PR
Documentation guide for QtPass - README, FAQ, localization
Release workflow for QtPass - versioning, builds, publishing
| name | qtpass-localization |
| description | QtPass localization workflow - translation files, updating, adding languages |
| license | GPL-3.0-or-later |
| metadata | {"audience":"developers","workflow":"localization"} |
Location: localization/localization_<lang>.ts
Qt uses Qt Linguist (.ts files) for translations.
Always re-derive the current list at the moment of work, since new locales land regularly:
ls localization/localization_*.ts | sed 's|.*localization_||;s|\.ts$||'
Per-locale completion stats (finished / unfinished-with-text / empty):
python3 -c "
import re, glob, os
for f in sorted(glob.glob('localization/localization_*.ts')):
loc = os.path.basename(f).replace('localization_','').replace('.ts','')
content = open(f).read()
fin=ut=ue=0
for m in re.finditer(r'<translation([^>]*)>(.*?)</translation>', content, re.DOTALL):
a,b = m.group(1), m.group(2)
if 'vanished' in a: continue
if 'unfinished' in a:
if b.strip(): ut += 1
else: ue += 1
else: fin += 1
print(f'{loc:8s} {fin:3d}/{fin+ut+ue} ut={ut:3d} empty={ue}')
"
unfinished-with-text means a translation exists but is flagged for native-speaker review (Weblate convention).
When source files change (strings added, moved, or refactored), run qmake to update translations:
# IMPORTANT: Run distclean first to avoid stale generated files (ui_*.h) in translations
make distclean
# Run qmake to update translations (uses lupdate internally)
qmake6
This updates all .ts files with:
<translation> tags)# Create branch
git checkout -b chore/update-localization
# Add and commit
git add localization/
git commit -m "chore: update localization source references"
# Push and create PR
git push -u origin chore/update-localization
gh pr create --title "chore: update localization source references" --body "## Summary
- Updated translation files with current source texts and line numbers
- Translations marked unfinished where source text changed
"
When you add new user-facing strings in C++:
// Use tr() for translatable strings
ui->label->setText(tr("New text here"));
localization/localization_en_US.ts in Qt Linguisttr() for all user-facing strings%1, %2, %3\n for line breaks// Good
tr("Delete %1?").arg(filename)
tr("Found %n password(s)", count)
// Avoid
tr("Delete " + filename + "?") // Can't be translated properly
Don't copy localization_en_US.ts — that imports stale English-as-translation strings that have to be cleared one by one. Bootstrap an empty skeleton and let lupdate (driven by qmake6) populate it with the current source strings as type="unfinished".
Prefer the language-only code (e.g. fa, ar, de) when the translation is the standard form understood across regions (Modern Standard Arabic, Standard German, Standard Persian).
Qt's locale fallback maps fa_IR, ar_MA, de_AT etc. to the language-only file automatically — one fa.ts covers Iran, Afghanistan (Dari), and every other Persian-speaking user.
Use the xx_YY form only when the translation is genuinely region-specific (Brazilian vs European Portuguese, Simplified vs Traditional Chinese, regional Spanish variants).
Create a 4-line skeleton (lupdate refuses to populate a 0-byte file):
cat > localization/localization_<lang>.ts <<'EOF'
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="<lang>">
</TS>
EOF
Register it in src/src.pro (alphabetically) under TRANSLATIONS +=. Note: qtpass.pro is the parent project file; the actual list lives in src/src.pro.
Run make distclean && qmake6 — the build's lupdate step populates the file with all 304 source strings as type="unfinished". Confirm via:
qmake6 2>&1 | grep "localization_<lang>.ts"
# -> Updating 'localization/localization_<lang>.ts'...
The skeleton ships as-is once the file is populated by lupdate: all entries type="unfinished", ready for Weblate translators to fill. A bootstrap PR does not need pre-filled best-effort translations — empty <translation type="unfinished"> is the deliverable.
The "fill empty entries with best-effort, mark type="unfinished"" guidance applies only when patching an existing locale flagged by reviewers — not to fresh-skeleton bootstrap PRs.
Optionally hand-edit a few high-value strings (window title, About box) via Qt Linguist before pushing, then let Weblate handle the rest.
Adding Persian (fa) for Iranian/Afghan/Tajik users involved exactly: 4-line skeleton, one TRANSLATIONS += line in src/src.pro, one make distclean && qmake6. No copy step. Result: 304 entries marked type="unfinished" (302 empty + 2 numerusform plural skeletons), ready for Weblate.
# For development (or part of build process)
lrelease localization/*.ts
# Or via qmake
make translations
QtPass uses system locale. To test:
# Linux
LANG=nl_NL ./qtpass
# Or set in QtPass settings
// Enable verbose translation debugging
QTranslator translator;
if (translator.load(QLocale(), "qtpass", "_", ":/languages")) {
qDebug() << "Loaded translation for:" << QLocale().name();
}
tr() - fix in sourceWhen merging PRs with translation updates:
# Fetch and rebase
git fetch origin
git pull origin main --rebase
# Resolve conflicts in .ts files (usually just XML merge)
# Test with qmake6
# Format with prettier if needed
npx prettier --write localization/*.ts
Always run make distclean before qmake6 when updating translations:
# Wrong - may include stale generated files in translations
qmake6
# Correct - starts fresh
make distclean
qmake6
If you added new tr() strings but they don't appear:
qmake6 to update the .ts filesWhen source strings change, translations are marked "unfinished" but may still appear correct. Always check:
%1, %2) matchesWhen merging translation PRs that conflict:
# Use theirs strategy for .ts files (they're XML, prefer incoming)
git checkout --theirs localization/localization_*.ts
git add localization/
git commit -m "Resolve merge conflict - use theirs for translations"
Translations are normally managed via Weblate. However, direct .ts edits are appropriate for:
type="unfinished" so Weblate native reviewers can refine them.klipboard/klipbordas/Tabela e kopjimit onto a single term).When in doubt, fix and mark type="unfinished" so Weblate review can iterate. Only run qmake6 to update source strings (English) when code changes — that step is mechanical and affects every locale file.
Qt UI strings use & (rendered as & in .ts XML) before a letter to mark a keyboard accelerator (e.g., &Quit underlines Q so Alt+Q triggers it). Conventions per script family:
&Letter — &File, &Edit, &Quit. Pick a non-conflicting letter; within a single menu, mnemonics must be unique.(&L) text — (&F) 文件, (&Q) 退出. Don't rely on native characters as mnemonics.(&L) text (preferred for portability) or native-letter &letter.&Покажи) work if the user's keyboard supports them, otherwise use (&L) text parens form.Preserve the source mnemonic letter in the translation when feasible, so the same shortcut works across locales. When this is not possible, choose a non-conflicting letter from the translated string.
Audit for missing mnemonics:
python3 -c "
import re, glob
mnemonic_sources = {'&Use pass','Nati&ve Git/GPG','&Show','&Hide','&Restore','&Quit','Mi&nimize','Ma&ximize'}
for f in sorted(glob.glob('localization/localization_*.ts')):
content = open(f).read()
for m in re.finditer(r'<message[^>]*>.*?<source>(.*?)</source>.*?<translation([^>]*)>(.*?)</translation>', content, re.DOTALL):
src,a,t = m.group(1), m.group(2), m.group(3)
if 'vanished' in a or src not in mnemonic_sources or not t.strip(): continue
if not re.search(r'&[^\s&;]', t):
print(f'{f}: {src} -> {t}')
"
Translation review tends to come in batches of ~5 findings at a time (CodeRabbit, GitHub AI quality findings, native-speaker comments on PRs). Standard handling:
.ts.qtpass-localization-audit skill) before pushing — placeholder format and HTML balance are non-obvious bugs that the reviewer's prose description may not catch.type="unfinished" unless a native speaker has confirmed them. Weblate finalises them on review.When static analysis flags translation issues (e.g., filename preservation):
# Fetch the PR branch
git fetch origin refs/pull/<PR>/head:pr/<PR>-fix
git checkout pr/<PR>-fix
# Fix the translation
# Edit the .ts file to preserve exact filename/token
# Commit with signing and push
git add localization/localization_<lang>.ts
git commit -S -m "fix: preserve .gpg-id filename in <lang> translation"
git push -u origin pr/<PR>-fix
Translation PRs often use squash merge:
gh pr merge <PR_NUMBER> --squash --auto --delete-branch