Compare commits

..

6 Commits

Author SHA1 Message Date
Weblate
a98b5a4e39 Translated using Weblate (French)
All checks were successful
Translations / check (pull_request) Successful in 29s
CI / ci (pull_request) Successful in 10m50s
Currently translated at 52.8% (231 of 437 strings)

Translation: Calendula/Strings
Translate-URL: https://weblate.dev.jeanlucmakiola.de/projects/calendula/strings/fr/
2026-07-19 00:57:38 +00:00
augustin64
98ca454361 Translated using Weblate (French)
Currently translated at 52.8% (231 of 437 strings)

Translation: Calendula/Strings
Translate-URL: https://weblate.dev.jeanlucmakiola.de/projects/calendula/strings/fr/
2026-07-19 00:57:38 +00:00
Pablo
3bbc5afe4b Translated using Weblate (Russian)
All checks were successful
Translations / check (pull_request) Successful in 27s
CI / ci (pull_request) Successful in 4m31s
Currently translated at 14.1% (62 of 437 strings)

Translation: Calendula/Strings
Translate-URL: https://weblate.dev.jeanlucmakiola.de/projects/calendula/strings/ru/
2026-07-18 08:20:20 +00:00
Weblate
f99f6d3e1a Translated using Weblate (Polish)
All checks were successful
Translations / check (pull_request) Successful in 5s
CI / ci (pull_request) Successful in 4m32s
Currently translated at 100.0% (437 of 437 strings)

Translation: Calendula/Strings
Translate-URL: https://weblate.dev.jeanlucmakiola.de/projects/calendula/strings/pl/
2026-07-15 21:56:12 +00:00
Bazyli Cyran
b959de2a14 Translated using Weblate (Polish)
Currently translated at 100.0% (437 of 437 strings)

Translation: Calendula/Strings
Translate-URL: https://weblate.dev.jeanlucmakiola.de/projects/calendula/strings/pl/
2026-07-15 21:56:12 +00:00
Bazyli Cyran
f8ea8d8508 Translated using Weblate (Polish)
All checks were successful
Translations / check (pull_request) Successful in 6s
CI / ci (pull_request) Successful in 12m13s
Currently translated at 97.2% (417 of 429 strings)

Translation: Calendula/Strings
Translate-URL: https://weblate.dev.jeanlucmakiola.de/projects/calendula/strings/pl/
2026-07-15 18:54:10 +00:00
4 changed files with 11 additions and 116 deletions

View File

@@ -1,13 +1,11 @@
name: Release — F-Droid repo + Gitea/Codeberg release name: Release — F-Droid repo + Gitea release
# A release is cut by merging a release branch into main with a bumped # A release is cut by merging a release branch into main with a bumped
# versionName (see docs/RELEASING.md). This workflow reads that versionName and, # versionName (see docs/RELEASING.md). This workflow reads that versionName and,
# if no matching tag exists yet, runs tests, builds + signs the APK, publishes # if no matching tag exists yet, runs tests, builds + signs the APK, publishes
# it to the F-Droid repo, creates the vX.Y.Z tag + Gitea release, and mirrors # it to the F-Droid repo, and only then creates the vX.Y.Z tag + Gitea release
# that release to Codeberg with the signed APK + a SHA-256 checksum as a # itself — the tag is an output of the pipeline, not its trigger. Ordinary
# direct-download channel — the tag is an output of the pipeline, not its # merges (no version bump) fall through `detect` and do nothing.
# trigger. Ordinary merges (no version bump) fall through `detect` and do
# nothing.
# #
# A manual workflow_dispatch (from a branch) runs the re-sign-only recovery # A manual workflow_dispatch (from a branch) runs the re-sign-only recovery
# path: it re-signs the existing F-Droid index with the repo key and re-uploads, # path: it re-signs the existing F-Droid index with the repo key and re-uploads,
@@ -352,88 +350,3 @@ jobs:
curl -s -X POST -H "Authorization: token $TOKEN" \ curl -s -X POST -H "Authorization: token $TOKEN" \
-F "attachment=@/tmp/$ASSET" \ -F "attachment=@/tmp/$ASSET" \
"$API/releases/$ID/assets?name=$ASSET" -o /dev/null -w "asset upload HTTP %{http_code}\n" "$API/releases/$ID/assets?name=$ASSET" -o /dev/null -w "asset upload HTTP %{http_code}\n"
# Mirror the release to the Codeberg mirror as a direct-download channel
# for users who don't want F-Droid. Gitea already push-mirrors branches +
# tags to Codeberg, but releases aren't git objects so they don't sync —
# we create the release there over the API and attach the signed APK plus
# a SHA-256 checksum. The APK is identical to the F-Droid one (same app
# key), so this adds no trust surface. Best-effort: a Codeberg outage
# (it 504s under load) must never fail an already-published F-Droid
# release. Needs the CODEBERG_RELEASE_TOKEN secret; skips cleanly if unset.
- name: Publish release to Codeberg
if: env.IS_RELEASE == 'true'
continue-on-error: true
env:
TOKEN: ${{ secrets.CODEBERG_RELEASE_TOKEN }}
API: https://codeberg.org/api/v1/repos/jlmakiola/calendula
SHA: ${{ github.sha }}
run: |
set -e
if [ -z "${TOKEN:-}" ]; then
echo "CODEBERG_RELEASE_TOKEN not set — skipping Codeberg publish."
exit 0
fi
TAG="v$VERSION"
APK="app/build/outputs/apk/release/app-release.apk"
if [ ! -f "$APK" ]; then echo "No release APK found — skipping." >&2; exit 1; fi
ASSET_APK="calendula_v${VERSION}.apk"
ASSET_SUM="${ASSET_APK}.sha256"
cp "$APK" "/tmp/$ASSET_APK"
( cd /tmp && sha256sum "$ASSET_APK" > "$ASSET_SUM" )
# Release notes: reuse the section extracted for the Gitea release,
# fall back to the CHANGELOG entry if that step's file is gone.
if [ ! -s release-notes.md ]; then
awk -v ver="$VERSION" '
$0 ~ "^## \\[" ver "\\]" { flag = 1; next }
/^## \[/ { flag = 0 }
flag' CHANGELOG.md > release-notes.md
sed -i -e '/./,$!d' release-notes.md
fi
[ -s release-notes.md ] || echo "_See CHANGELOG.md for ${VERSION}._" > release-notes.md
# The pipeline creates the tag via the Gitea API, which the push mirror
# (sync_on_commit only fires on real git pushes) doesn't propagate
# promptly — so a release POST that carries a target_commitish can
# outrun the mirror and 500 on a commit/tag Codeberg hasn't received.
# Push the tag straight to Codeberg so it's guaranteed present, then
# attach the release to that existing tag with NO target_commitish
# (which is what triggered the 500).
git tag -f "$TAG" "$SHA"
git push -f "https://jlmakiola:${TOKEN}@codeberg.org/jlmakiola/calendula.git" \
"refs/tags/$TAG"
python3 - "$TAG" <<'PY' > cb-payload.json
import json, sys
print(json.dumps({
"tag_name": sys.argv[1],
"name": sys.argv[1],
"body": open("release-notes.md").read(),
"draft": False,
"prerelease": False,
}))
PY
# Upsert (re-run safe). POST also creates the tag at target_commitish
# if the push mirror hasn't synced it yet.
ID=$(curl -s -H "Authorization: token $TOKEN" "$API/releases/tags/$TAG" | jq -r '.id // empty')
if [ -n "$ID" ]; then
curl -s -o /dev/null -w "release PATCH HTTP %{http_code}\n" -X PATCH \
-H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
-d @cb-payload.json "$API/releases/$ID"
else
curl -s -o cb-response.json -w "release POST HTTP %{http_code}\n" -X POST \
-H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
-d @cb-payload.json "$API/releases"
ID=$(jq -r '.id // empty' cb-response.json 2>/dev/null || true)
fi
if [ -z "$ID" ]; then echo "Could not resolve Codeberg release id." >&2; exit 1; fi
# Attach APK + checksum, replacing any prior asset of the same name.
for A in "$ASSET_APK" "$ASSET_SUM"; do
OLD=$(curl -s -H "Authorization: token $TOKEN" "$API/releases/$ID/assets" \
| jq -r --arg n "$A" '.[] | select(.name==$n) | .id')
[ -n "$OLD" ] && curl -s -X DELETE -H "Authorization: token $TOKEN" "$API/releases/$ID/assets/$OLD" >/dev/null || true
curl -s -X POST -H "Authorization: token $TOKEN" \
-F "attachment=@/tmp/$A" \
"$API/releases/$ID/assets?name=$A" -o /dev/null -w "asset $A HTTP %{http_code}\n"
done
echo "Published $TAG to Codeberg."

View File

@@ -475,21 +475,19 @@
<string name="special_dates_default_title_anniversary">Jahrestag von {name} ({year})</string> <string name="special_dates_default_title_anniversary">Jahrestag von {name} ({year})</string>
<string name="special_dates_default_title_custom">{name}</string> <string name="special_dates_default_title_custom">{name}</string>
<string name="settings_week_numbers">Kalenderwochen</string> <string name="settings_week_numbers">Kalenderwochen</string>
<string name="settings_week_numbers_summary">Kalenderwochen in Monatsansicht zeigen</string> <string name="settings_week_numbers_summary">Kalenderwochen der Monatsansicht anzeigen</string>
<string name="calendars_export_title">Kalender exportieren</string> <string name="calendars_export_title">Kalender exportieren</string>
<string name="calendars_export_hint">Wähle aus, welche Kalender in die .ics-Datei aufgenommen werden sollen.</string> <string name="calendars_export_hint">Wähle aus, welche Kalender in die .ics-Datei aufgenommen werden sollen.</string>
<string name="calendars_export_action">Exportieren</string> <string name="calendars_export_action">Exportieren</string>
<string name="calendars_restore_header">Wiederherstellen</string> <string name="calendars_restore_header">Wiederherstellen</string>
<string name="calendars_restore_action">Aus .ics-Datei wiederherstellen</string> <string name="calendars_restore_action">Aus .ics-Datei wiederherstellen</string>
<string name="calendars_restore_hint">Einträge von einem Backup oder einer anderen Kalender App importieren.</string> <string name="calendars_restore_hint">Importiere Ereignisse aus einem Backup oder einer anderen Kalender-App.</string>
<string name="import_done_dedup_note">Bereits im Kalender vorhandene Ereignisse wurden übersprungen.</string> <string name="import_done_dedup_note">Bereits im Kalender vorhandene Ereignisse wurden übersprungen.</string>
<string name="import_done_added_label">Hinzugefügt</string> <string name="import_done_added_label">Hinzugefügt</string>
<string name="import_done_skipped_label">Dopplungen</string> <string name="import_done_skipped_label">Duplikate</string>
<string name="import_button">Importieren</string> <string name="import_button">Importieren</string>
<plurals name="import_title_count"> <plurals name="import_title_count">
<item quantity="one">%d Ereignis wird importiert</item> <item quantity="one">%d Ereignis wird importiert</item>
<item quantity="other">%d Ereignisse werden importiert</item> <item quantity="other">%d Ereignisse werden importiert</item>
</plurals> </plurals>
<string name="event_detail_duplicate">Duplikate</string>
<string name="reminder_day_tomorrow">Morgen</string>
</resources> </resources>

View File

@@ -82,24 +82,9 @@ release work when a merge actually cuts a release:
merged commit, build & sign the release APK with the **app key**, copy it into merged commit, build & sign the release APK with the **app key**, copy it into
the F-Droid repo, generate the per-version changelog, re-sign the index with the F-Droid repo, generate the per-version changelog, re-sign the index with
the **repo key**, upload `repo/` + `metadata/`, then create the `vX.Y.Z` tag + the **repo key**, upload `repo/` + `metadata/`, then create the `vX.Y.Z` tag +
Gitea release (CHANGELOG section as notes), attach the R8 `mapping.txt`, and Gitea release (CHANGELOG section as notes) and attach the R8 `mapping.txt`
mirror the release to **Codeberg** with the signed APK + a SHA-256 checksum (best-effort). Ordinary merges with no version bump fall through `detect` and
(both best-effort). Ordinary merges with no version bump fall through `detect` do nothing.
and do nothing.
### Codeberg direct-download channel
Alongside F-Droid, each release is mirrored to the Codeberg repo
(`jlmakiola/calendula`) as a plain download for users who don't want F-Droid.
Gitea already **push-mirrors** branches and tags to Codeberg, but releases
aren't git objects and don't sync, so the pipeline creates the release over the
Codeberg API and attaches `calendula_v<version>.apk` + its `.sha256`. It's the
same APK the F-Droid repo serves (same **app key**), so it adds no trust
surface. The step is best-effort: a Codeberg outage never fails an
already-published F-Droid release, and it skips cleanly if `CODEBERG_RELEASE_TOKEN` is
unset. One-time setup: the repo's **Releases** unit must be enabled and a
`CODEBERG_RELEASE_TOKEN` secret (Codeberg access token, `write:repository` scope) added
to Gitea Actions.
### Manual re-sign / recovery ### Manual re-sign / recovery
@@ -118,7 +103,6 @@ for key rotation or repo recovery without publishing a new app version.
| `FDROID_CONFIG_BASE64` | F-Droid `config.yml` (base64) — repo metadata + keystore passwords. | | `FDROID_CONFIG_BASE64` | F-Droid `config.yml` (base64) — repo metadata + keystore passwords. |
| `HETZNER_HOST`, `HETZNER_USER`, `HETZNER_PASS` | Upload target for the F-Droid repo. | | `HETZNER_HOST`, `HETZNER_USER`, `HETZNER_PASS` | Upload target for the F-Droid repo. |
| `GITHUB_TOKEN` | Provided by Gitea Actions; used to create the release + attach assets. | | `GITHUB_TOKEN` | Provided by Gitea Actions; used to create the release + attach assets. |
| `CODEBERG_RELEASE_TOKEN` | Codeberg access token (`write:repository` scope) — creates the mirrored Codeberg release + uploads the APK/checksum. Best-effort; if unset the Codeberg step skips. |
The two keys are independent: the **app key** signs APKs; the **repo key** The two keys are independent: the **app key** signs APKs; the **repo key**
signs the index (its fingerprint is what users pin). Neither key nor the signs the index (its fingerprint is what users pin). Neither key nor the

View File

@@ -1,6 +1,6 @@
[versions] [versions]
agp = "9.2.1" agp = "9.2.1"
kotlin = "2.4.10" kotlin = "2.3.21"
ksp = "2.3.9" ksp = "2.3.9"
hilt = "2.59.2" hilt = "2.59.2"
coreKtx = "1.19.0" coreKtx = "1.19.0"