mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-14 18:26:11 -04:00
Compare commits
87
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a57257f03 | ||
|
|
fc4d2674e9 | ||
|
|
1a176e3df8 | ||
|
|
1fff40b9d4 | ||
|
|
9a9d4452d1 | ||
|
|
43740d8b2e | ||
|
|
83849efe80 | ||
|
|
ed66731207 | ||
|
|
a41bef21b5 | ||
|
|
91b49536b7 | ||
|
|
8690072ee2 | ||
|
|
92c829c7e6 | ||
|
|
1c67a78130 | ||
|
|
7a64a23d1e | ||
|
|
e2fcbbfdfd | ||
|
|
29639cdcd1 | ||
|
|
d8a6ef2286 | ||
|
|
e68927595c | ||
|
|
aa928de265 | ||
|
|
3662904d75 | ||
|
|
cec36268e0 | ||
|
|
5895ccbc27 | ||
|
|
798ac32ec9 | ||
|
|
6ed0bf8257 | ||
|
|
39a6c3c89f | ||
|
|
2ba90e1d18 | ||
|
|
92dac661c4 | ||
|
|
7712b18388 | ||
|
|
d7988189a0 | ||
|
|
ff5d0cedfa | ||
|
|
f82adeba42 | ||
|
|
cc99ad59ab | ||
|
|
b66c756741 | ||
|
|
fbbdb4e131 | ||
|
|
0bf6bf7b5f | ||
|
|
865a609203 | ||
|
|
9f213cf499 | ||
|
|
1330c77ed8 | ||
|
|
2356726417 | ||
|
|
03c09917da | ||
|
|
70830cf2ca | ||
|
|
43140005f2 | ||
|
|
59e2a16a39 | ||
|
|
11d6ddf3fa | ||
|
|
b10b42eede | ||
|
|
5801b8ea56 | ||
|
|
b9461d3259 | ||
|
|
7960144be6 | ||
|
|
0f95538f39 | ||
|
|
fec512175b | ||
|
|
a427fcbf4e | ||
|
|
c49cd62b2a | ||
|
|
abf28f335c | ||
|
|
540e6d1dbc | ||
|
|
76e1128e93 | ||
|
|
cbf681ccfc | ||
|
|
30f17b4791 | ||
|
|
cd6aaaf165 | ||
|
|
289dcd660e | ||
|
|
b07428f894 | ||
|
|
dbd1d422c6 | ||
|
|
0b83e86c5e | ||
|
|
b396ce457d | ||
|
|
734e15c94d | ||
|
|
edf84248ab | ||
|
|
83dfdb9219 | ||
|
|
70b9c2981e | ||
|
|
971fcbd34c | ||
|
|
cebed0a60d | ||
|
|
8817da309f | ||
|
|
27cc0e8d00 | ||
|
|
cecd3088be | ||
|
|
412ea369f1 | ||
|
|
22ca3a2a46 | ||
|
|
92feea8a10 | ||
|
|
3af1af872e | ||
|
|
1f00925cd8 | ||
|
|
50079e2b7a | ||
|
|
02ec76980c | ||
|
|
52ad90f77e | ||
|
|
563c6e71f7 | ||
|
|
a357d77b00 | ||
|
|
abc82b0f09 | ||
|
|
3e6541deec | ||
|
|
33a972001e | ||
|
|
fe67ce0365 | ||
|
|
511944c21c |
@@ -21,3 +21,5 @@ When adding new user-facing strings:
|
||||
- `troubleshooter_ble_result_failure_title` (status/error title)
|
||||
|
||||
Common prefixes currently in use: `device_`, `settings_`, `support_`, `profiles_`, `press_`, `general_`, `pods_`, `upgrade_`, `widget_`, `debug_`, `permission_`, `troubleshooter_`, `overview_`, `anc_`, `onboarding_`. There is no `error_*` prefix — error labels live under the relevant feature (e.g. `general_error_label`, `troubleshooter_*_failure_*`).
|
||||
|
||||
String context, character limits and file context are managed on Crowdin through the android-translation plugin's `crowdin-annotate` skill. XML comments in `values/strings.xml` no longer reach translators once a string's context has been written on Crowdin; change it there.
|
||||
|
||||
@@ -32,13 +32,15 @@ ScreenshotContent.kt (mock data + composables)
|
||||
|------|---------|
|
||||
| `app/src/debug/java/.../screenshots/ScreenshotContent.kt` | Mock data composables (7 exist; `HomescreenWidgetContent` has an IDE preview only and is **not** in the Play Store pipeline) |
|
||||
| `app/src/screenshotTest/kotlin/.../screenshots/PlayStoreScreenshots.kt` | `@PreviewTest` functions (currently: `DashboardLight`, `DashboardDark`, `CasePopUp`, `DeviceProfiles`, `AddProfile`, `DeviceSettingsReactions`, `WidgetConfiguration`) |
|
||||
| `app/src/screenshotTest/kotlin/.../screenshots/PlayStoreLocales.kt` | Multi-preview annotations (auto-generated by batch script) |
|
||||
| `app/src/screenshotTest/kotlin/.../screenshots/PlayStoreLocales.kt` | Multi-preview annotations. The committed content is an en-US placeholder, not meaningful data — `generate_screenshots.sh` rewrites it per batch and restores it from a `.bak` on exit. A run killed hard leaves that `.bak` behind, so the script now refuses to start until it is restored by hand |
|
||||
| `fastlane/generate_screenshots.sh` | Batched generation; locale list (`ALL_LOCALES`) and `BATCH_SIZE` are defined inside the script |
|
||||
| `fastlane/copy_screenshots.sh` | Copies rendered PNGs into fastlane structure |
|
||||
|
||||
## Commit policy
|
||||
|
||||
Only the 6 smoke locales (en-US, de-DE, ja-JP, ar, zh-CN, pt-BR) have `phoneScreenshots/*.png` checked into the repo. Non-smoke locales are excluded by `.gitignore`. This mirrors permission-pilot and keeps repo size small (~7 MB vs ~67 MB for the full 68 locales).
|
||||
Only `en-US` has `phoneScreenshots/*.png` checked into the repo — 7 PNGs, ~1 MB tracked. Every other locale is excluded by `.gitignore`.
|
||||
|
||||
`--smoke` still *renders* 6 locales (en-US, de-DE, ja-JP, ar, zh-CN, pt-BR), but only en-US is committed. The other five cover LTR, RTL and CJK layout so a render that breaks on non-Latin script fails during generation, and the resulting PNGs sit in the working tree for manual inspection. Nothing compares them against a baseline, so this is render coverage plus eyeballing, not regression checking.
|
||||
|
||||
Play Store's `supply` only uploads what's present in `fastlane/metadata/android/<locale>/images/phoneScreenshots/`. For locales not in the upload, Play Store retains whatever was last pushed. So full localization on Play Store is maintained by an **occasional manual** full regen + `:screenshots_only` upload — not by every PR.
|
||||
|
||||
@@ -90,11 +92,20 @@ Periodic, manual operation — not per-PR:
|
||||
```bash
|
||||
./fastlane/generate_screenshots.sh # full, ~30 min, 476 PNGs (68 locales x 7)
|
||||
./fastlane/copy_screenshots.sh --clean
|
||||
bundle exec fastlane screenshots_only # uploads all 68 locales to Play Store
|
||||
git checkout -- fastlane/metadata/android/ # discard non-smoke changes (gitignored anyway)
|
||||
git add fastlane/metadata/android/en-US/images/phoneScreenshots/
|
||||
if bundle exec fastlane screenshots_only; then
|
||||
git checkout -- fastlane/metadata/android/ &&
|
||||
git commit --only -m "chore(screenshots): Refresh Play Store screenshots" -- \
|
||||
fastlane/metadata/android/en-US/images/phoneScreenshots/
|
||||
else
|
||||
git checkout -- fastlane/metadata/android/
|
||||
echo "Upload failed; the refreshed en-US screenshots remain staged for retry."
|
||||
fi
|
||||
```
|
||||
|
||||
The `.gitignore` rule keeps non-smoke output unstaged automatically, so only the smoke locales' refreshed PNGs would show up as modifications and can be committed.
|
||||
The `git add` has to happen before the upload. The final `git checkout` restores every tracked file under that path **from the index**, so staging the refreshed English set is precisely what makes it survive the checkout — skip the `git add` and the checkout silently reverts the refresh while the store still receives the new images.
|
||||
|
||||
Restoring is the checkout's job otherwise: `screenshots_only` runs `remove_unsupported_languages.sh`, which deletes 9 tracked locale directories (es-AR, sc-IT, sq-AL, uz, kmr-TR, ur-IN, zu, si-LK, nb) from the working tree before uploading — 35 tracked files, a subset of the 309 tracked non-screenshot metadata files under that path, all of them put back by the checkout. It does **not** touch the regenerated non-English PNGs: those are untracked and ignored, so they stay on disk and never show up in `git status`. Because the checkout discards any uncommitted metadata text edits too, run this refresh only with an otherwise-clean metadata tree. The final commit is path-limited on purpose, so an unrelated staged change can't ride along, and it is gated on `screenshots_only` succeeding rather than merely sequenced after it: if the upload fails, the refreshed English files stay staged for a retry instead of being committed as though they were deployed. The deleted locale directories are restored on either path.
|
||||
|
||||
## Technical Notes
|
||||
|
||||
|
||||
@@ -7,13 +7,12 @@
|
||||
"deny": []
|
||||
},
|
||||
"enabledPlugins": {
|
||||
"android-translation@claude-code-cafe": true,
|
||||
"debugbadger@claude-code-cafe": true,
|
||||
"jvm-tools@claude-code-cafe": true,
|
||||
"frontend-design@claude-plugins-official": true,
|
||||
"kotlin-lsp@claude-plugins-official": true,
|
||||
"support-investigator@claude-code-cafe": true,
|
||||
"google-play@claude-code-cafe": true,
|
||||
"devtools@claude-code-cafe": true
|
||||
"android-translation@clanker-cafe": true,
|
||||
"debugbadger@clanker-cafe": true,
|
||||
"jvm-tools@clanker-cafe": true,
|
||||
"support@clanker-cafe": true,
|
||||
"google-play@clanker-cafe": true,
|
||||
"devtools@clanker-cafe": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,13 +72,13 @@ bats tools/release/bump.bats
|
||||
|
||||
| Tag suffix | FOSS APK | GitHub release | Fastlane lane | Play track | Rollout |
|
||||
|---|---|---|---|---|---|
|
||||
| `-beta*` | `assembleFossBeta` | pre-release | `beta` | `beta` | 10% |
|
||||
| `-rc*` | `assembleFossRelease` | full release | `production` | **`beta`** | 10% |
|
||||
| `-beta*` | `assembleFossBeta` | pre-release | `beta` | `beta` | 100% |
|
||||
| `-rc*` | `assembleFossRelease` | full release | `production` | **`beta`** | 100% |
|
||||
|
||||
`release-tag.yml` accepts only `v<M.m.p>-rcN` or `v<M.m.p>-betaN` — any other suffix fails
|
||||
`validate-tag` before a build starts. There is no third channel.
|
||||
|
||||
`lane :production` in `Fastfile` uploads to Play's **beta** track at 10% — manually promoted to production via Play Console.
|
||||
`lane :production` in `Fastfile` uploads to Play's **beta** track at 100% — manually promoted to production via Play Console.
|
||||
|
||||
## Rollback
|
||||
|
||||
|
||||
@@ -193,4 +193,15 @@ jobs:
|
||||
env:
|
||||
STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
|
||||
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
|
||||
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
||||
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
||||
|
||||
# The Play flavor is obfuscated; the bundle embeds this mapping, but keep it reachable
|
||||
# for retracing user-submitted logs without going through Play Console.
|
||||
- name: Archive R8 mapping
|
||||
if: always() && !(github.event_name == 'workflow_dispatch' && inputs.dry_run)
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1
|
||||
with:
|
||||
name: r8-mapping-${{ github.ref_name }}
|
||||
path: app/build/outputs/mapping/gplay*/mapping.txt
|
||||
if-no-files-found: warn
|
||||
overwrite: true
|
||||
|
||||
@@ -23,4 +23,4 @@ jobs:
|
||||
thumbnail:
|
||||
permissions:
|
||||
issues: write
|
||||
uses: d4rken-org/.github/.github/workflows/thumbnail-images.yml@3756372c3c844e4817c03d27983691f9991a19f4
|
||||
uses: d4rken-org/.github/.github/workflows/thumbnail-images.yml@94e36d9a67a887e24338f95fd0429925cea21c98
|
||||
|
||||
+1
-6
@@ -17,16 +17,11 @@
|
||||
.kotlin
|
||||
# Screenshot test reference images (ephemeral, regenerated on demand)
|
||||
app/src/screenshotTest*/reference/
|
||||
# Play Store phone screenshots: commit only the 6 smoke locales (mirrors permission-pilot).
|
||||
# Play Store phone screenshots: commit only en-US.
|
||||
# Full localization is uploaded by occasional manual regen + screenshots_only;
|
||||
# Play Store retains previously-uploaded screenshots for locales not pushed.
|
||||
fastlane/metadata/android/*/images/phoneScreenshots/*.png
|
||||
!fastlane/metadata/android/en-US/images/phoneScreenshots/*.png
|
||||
!fastlane/metadata/android/de-DE/images/phoneScreenshots/*.png
|
||||
!fastlane/metadata/android/ja-JP/images/phoneScreenshots/*.png
|
||||
!fastlane/metadata/android/ar/images/phoneScreenshots/*.png
|
||||
!fastlane/metadata/android/zh-CN/images/phoneScreenshots/*.png
|
||||
!fastlane/metadata/android/pt-BR/images/phoneScreenshots/*.png
|
||||
.codex
|
||||
protocol-research/
|
||||
_site/
|
||||
|
||||
@@ -55,10 +55,12 @@ android {
|
||||
includeInApk = false
|
||||
includeInBundle = false
|
||||
}
|
||||
proguardFiles("proguard-rules-foss.pro")
|
||||
}
|
||||
create("gplay") {
|
||||
dimension = "version"
|
||||
signingConfig = signingConfigs["releaseGplay"]
|
||||
proguardFiles("proguard-rules-gplay.pro")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,6 +191,7 @@ dependencies {
|
||||
|
||||
addCompose()
|
||||
addGlance()
|
||||
addWorkerManager()
|
||||
addDataStore()
|
||||
addNavigation3()
|
||||
addSerialization()
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# The FOSS build is open source and ships on GitHub/F-Droid; readable stack traces are worth more
|
||||
# than smaller identifiers there. Only the Google Play flavor obfuscates (proguard-rules-gplay.pro).
|
||||
-dontobfuscate
|
||||
@@ -0,0 +1,21 @@
|
||||
# Google Play scores the obfuscation share of every uploaded bundle and restricts listings that
|
||||
# stay below its threshold, so the Play flavor obfuscates. The FOSS flavor does not
|
||||
# (proguard-rules-foss.pro).
|
||||
|
||||
# Keep stack traces retraceable with the mapping file that the bundle embeds.
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
-renamesourcefileattribute SourceFile
|
||||
|
||||
# AAP session logs identify settings and commands by their simple class name
|
||||
# (AapSessionEngine, AapAncController, AapOutboundController). Names only; unused classes are
|
||||
# still removed.
|
||||
-keepnames class eu.darken.capod.pods.core.apple.aap.protocol.AapSetting
|
||||
-keepnames class * extends eu.darken.capod.pods.core.apple.aap.protocol.AapSetting
|
||||
-keepnames class eu.darken.capod.pods.core.apple.aap.protocol.AapCommand
|
||||
-keepnames class * extends eu.darken.capod.pods.core.apple.aap.protocol.AapCommand
|
||||
|
||||
# ViewModel1 derives its log tag from the subclass name (VM:OverviewViewModel).
|
||||
-keepnames class * extends eu.darken.capod.common.uix.ViewModel1
|
||||
|
||||
# Error dialogs and log summaries show exceptions by class name (LocalizedError, asLogSummary).
|
||||
-keepnames class * extends java.lang.Throwable
|
||||
Vendored
-1
@@ -1,5 +1,4 @@
|
||||
-keep class eu.darken.capod.BuildConfig { *; }
|
||||
-dontobfuscate
|
||||
|
||||
# work-runtime 2.7.1 (pulled by Glance) uses Class.newInstance() reflection throughout.
|
||||
# R8 full mode strips no-arg constructors not reachable by static analysis.
|
||||
|
||||
@@ -47,6 +47,13 @@ internal enum class FossUpgradeView {
|
||||
STATUS_UPGRADED,
|
||||
}
|
||||
|
||||
// The Settings row and the PITCH title both read this one resource, so they cannot name the tier
|
||||
// differently — unlike the composed brand title, this stays a support ask ("Sponsor CAPod") rather
|
||||
// than naming the flavor. gplay's counterpart in UpgradeScreen.kt composes from brandTitleText
|
||||
// instead; each flavor implementation lives only in its own source set.
|
||||
@Composable
|
||||
internal fun settingsUpgradeStatusTitle(): String = stringResource(R.string.upgrade_foss_sponsor_label)
|
||||
|
||||
@Composable
|
||||
fun UpgradeScreenHost(
|
||||
route: Nav.Main.Upgrade = Nav.Main.Upgrade(),
|
||||
@@ -115,7 +122,7 @@ internal fun UpgradeScreen(
|
||||
// Status views describe the existing install, not a support ask — they get the composed
|
||||
// flavor title, with the postfix highlighted for supporters like the dashboard does it.
|
||||
title = if (view == FossUpgradeView.PITCH) {
|
||||
AnnotatedString(stringResource(R.string.settings_upgrade_status_label))
|
||||
AnnotatedString(settingsUpgradeStatusTitle())
|
||||
} else {
|
||||
// "CAPod FOSS", not "CAPod Pro": the FOSS flavor's own qualifier resource supplies the
|
||||
// tier word, so the title names this build. The upgraded gate keeps the highlight for
|
||||
|
||||
@@ -42,8 +42,10 @@ class UpgradeViewModel @Inject constructor(
|
||||
|
||||
// Which presentation the screen shows. The manage route (settings "upgrade status" entry)
|
||||
// gets a status view first; the pitch only appears once a free user asks for the upgrade
|
||||
// options. Upgrading wins over that choice — completing the sponsor flow from the pitch must
|
||||
// land on the upgraded status, not back on the ask. null until the route is bound.
|
||||
// options. Upgrading wins on EVERY route, not just manage: forced routes (Pro-locked settings)
|
||||
// stay open after the sponsor flow completes, and the pitch with its live sponsor button reads
|
||||
// as "sponsoring didn't work" to a fresh supporter — the toast alone is too transient for a
|
||||
// money moment without a receipt behind it. null until the route is bound.
|
||||
internal val state: StateFlow<State?> = combine(
|
||||
routeFlow,
|
||||
upgradeRepo.upgradeInfo,
|
||||
@@ -51,7 +53,7 @@ class UpgradeViewModel @Inject constructor(
|
||||
) { route, info, showOptions ->
|
||||
val view = when {
|
||||
route == null -> null
|
||||
route.manage && info.isPro -> FossUpgradeView.STATUS_UPGRADED
|
||||
info.isPro -> FossUpgradeView.STATUS_UPGRADED
|
||||
route.manage && !showOptions -> FossUpgradeView.STATUS_FREE
|
||||
else -> FossUpgradeView.PITCH
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Skenk</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Ek het reeds geskenk</string>
|
||||
<string name="foss_upgrade_no_money_label">Ek spandeer al my geld op AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS is gratis en oopbron. As u dit nuttig vind, oorweeg dit om ontwikkeling te befonds om die projek aan die gang te hou.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Borg ontwikkeling</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Geen advertensies. Geen opsporing. Geen Google Play-binding.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Ondersteuner sedert %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Dankie dat jy CAPod se ontwikkeling ondersteun!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Maak borgblad oop</string>
|
||||
<string name="settings_upgrade_status_label">Ondersteun CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Ondersteun CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Jou ondersteunersstatus.</string>
|
||||
<string name="upgrade_screen_why_title">Opgraderingsvoordele</string>
|
||||
<string name="upgrade_screen_how_title">Hoe om te help</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">ልገሳ</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">አስቀድሜ ልገሳ ፈጽሜአለሁ</string>
|
||||
<string name="foss_upgrade_no_money_label">ገንዘቤን በሙሉ በAirPods ላይ አውጥቻለሁ</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS ነፃ እና ክፍት ምንጭ ነው። ግኾሚ ሆኖ ካግኝት፣ ፕሮጀክቱ ቀጥሎ እንደይይድ ለመርዳት ላማቱን ማስደገፏ ያስቡ።</string>
|
||||
<string name="upgrade_foss_sponsor_action">ልማትን ይደግፉ</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">ምንም ማስታወቂያ የሉም። ምንም ክትትል የሉም። ምንም Google Play ቆለፋ የሉም።</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">ደጋፊ ከ%s ጀምሮ</string>
|
||||
<string name="upgrade_foss_supporter_thanks">የCAPod ልማትን ስለደገፉ እናመሰግናለን!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">የስፖንሰር ገጽን ክፈት</string>
|
||||
<string name="settings_upgrade_status_label">CAPod ን ደግፍ</string>
|
||||
<string name="upgrade_foss_sponsor_label">CAPod ን ደግፍ</string>
|
||||
<string name="settings_upgrade_status_description">የእርስዎ ደጋፊ ሁኔታ።</string>
|
||||
<string name="upgrade_screen_why_title">የማሻሻል ጥቅማጥቅሞች</string>
|
||||
<string name="upgrade_screen_how_title">እንዴት መርዳት እንደሚችሉ</string>
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">تبرع</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">لقد تبرعت بالفعل</string>
|
||||
<string name="foss_upgrade_no_money_label">أنفق كل أموالي على سماعات أيربودز</string>
|
||||
<string name="upgrade_foss_preamble">برنامج كابود FOSS مجاني ومفتوح المصدر. إذا وجدته مفيدًا، ففكر في دعم تطويره للمساعدة في استمرار المشروع.</string>
|
||||
<string name="upgrade_foss_sponsor_action">دعم التطوير</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">لا إعلانات. لا تتبع. لا قيود من جوجل بلاي.</string>
|
||||
<string name="upgrade_foss_preamble">برنامج كابود FOSS مجّاني ومفتوح المصدر. إذا وجدته مفيدًا، ففكّر في دعم تطويره للمساعدة في استمرار المشروع.</string>
|
||||
<string name="upgrade_foss_sponsor_action">ادعم التطوير</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">لا إعلانات. لا تتبّع. لا قيود من جوجل بلاي.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">هل عدت بالفعل؟ دعمك يبقي كابود حيًّا.</string>
|
||||
<string name="upgrade_foss_supporter_since">داعم منذ %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">شكرًا لدعمك تطوير CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">فتح صفحة الرعاية</string>
|
||||
<string name="settings_upgrade_status_label">دعم كابود</string>
|
||||
<string name="upgrade_foss_supporter_since">داعمٌ منذ %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">شكرًا لدعمك تطوير كابود!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">افتح صفحة الرعاية</string>
|
||||
<string name="upgrade_foss_sponsor_label">دعم كابود</string>
|
||||
<string name="settings_upgrade_status_description">حالة دعمك.</string>
|
||||
<string name="upgrade_screen_why_title">مزايا الترقية</string>
|
||||
<string name="upgrade_screen_how_title">كيفية المساعدة</string>
|
||||
<string name="upgrade_screen_how_body">كُن راعيًا وساهم في تطوير المشروع! اضغط على الزرّ أدناه لتفعيل جميع الميزات الإضافية وفتح ملفّي الشخصي على GitHub Sponsors.</string>
|
||||
<string name="upgrade_screen_how_body">كُن راعيًا وادعم تطوير المشروع! اضغط على الزرّ أدناه لتفعيل جميع المزايا الإضافية وفتح ملفّي الشخصي على GitHub Sponsors.</string>
|
||||
<string name="upgrade_screen_status_free_title">النسخة المجّانية</string>
|
||||
<string name="upgrade_screen_status_free_body">أنت تستخدم النسخة المجّانية من كابود. يمكنك فتح ميزات إضافية من خلال دعم التطوير.</string>
|
||||
<string name="upgrade_screen_status_free_action">الاطّلاع على خيارات الترقية</string>
|
||||
<string name="upgrade_screen_status_upgraded_title">الترقية نشطة</string>
|
||||
<string name="upgrade_screen_status_free_body">أنت تستخدم النسخة المجّانية من كابود. يمكنك فتح مزايا إضافيةٍ عبر دعم التطوير.</string>
|
||||
<string name="upgrade_screen_status_free_action">اطّلع على خيارات الترقية</string>
|
||||
<string name="upgrade_screen_status_upgraded_title">الترقية نشيطة</string>
|
||||
<string name="upgrade_screen_recurring_title">أبقِ التطبيق مستمرًّا</string>
|
||||
<string name="upgrade_screen_recurring_body">يستمرّ كابود في التطوّر من خلال التحديثات والإصلاحات. إذا كنت ترغب في دعم هذا التطوّر، ففكّر في التبرّع بشكل دوري عبر GitHub Sponsors.</string>
|
||||
<string name="upgrade_screen_recurring_body">يستمرّ كابود في التطوّر عبر التحديثات والإصلاحات. إذا كنت ترغب في دعم هذا التطوّر، ففكّر في التبرّع بشكلٍ دوريٍّ عبر GitHub Sponsors.</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">İanə ver</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Artıq ianə vermişəm</string>
|
||||
<string name="foss_upgrade_no_money_label">Bütün pulumu AirPods-lara xərcləyirəm</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS pulsuzdur və açıq mənbəlidir. Faydalı tapırsınızsa, layihənin davamına kömək etmək üçün inkişafı maliyyələşdirməyi düşünün.</string>
|
||||
<string name="upgrade_foss_sponsor_action">İnkişafı sponsor edin</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Reklam yoxdur. İzləmə yoxdur. Google Play bağlılığı yoxdur.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">%s tarixindən bəri dəstəkçi</string>
|
||||
<string name="upgrade_foss_supporter_thanks">CAPod-un inkişafını dəstəklədiyiniz üçün təşəkkürlər!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Sponsor səhifəsini aç</string>
|
||||
<string name="settings_upgrade_status_label">CAPod-u sponsorlaşdırın</string>
|
||||
<string name="upgrade_foss_sponsor_label">CAPod-u sponsorlaşdırın</string>
|
||||
<string name="settings_upgrade_status_description">Sizin tərəfdar statusu.</string>
|
||||
<string name="upgrade_screen_why_title">Yüksəltmə üstünləri</string>
|
||||
<string name="upgrade_screen_how_title">Necə kömək etmək</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Ахвяраваць</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Я ўжо ахвяраваў</string>
|
||||
<string name="foss_upgrade_no_money_label">Я патраціў усе грошы на AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS бясплатны і з адкрытым зыходным кодам. Калі вы лічыце яго карысным, разгледзьце магчымасць спансіравання распрацоўкі, каб дапамагчы трымаць праект ў жывых.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Падтрымаць распрацоўку</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Ніякай рэкламы. Ніякай адсочкі. Ніякай залежнасці ад Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Спонсар з %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Дзякуй за падтрымку развіцця CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Адкрыць старонку спонсарства</string>
|
||||
<string name="settings_upgrade_status_label">Спонсараваць CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Спонсараваць CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Ваш статус прыхільніка.</string>
|
||||
<string name="upgrade_screen_why_title">Перавагі абнаўлення</string>
|
||||
<string name="upgrade_screen_how_title">Як дапамагчы</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Дарение</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Вече съм дарил</string>
|
||||
<string name="foss_upgrade_no_money_label">Харча всичките си пари за AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS е безплатен и с отворен код. Ако ви е полезен, размислете да подкрепите разработката, за да помогнете проектът да продължи.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Спонсорирайте разработката</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Без реклами. Без следене. Без заключване в Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Поддържник от %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Благодарим ви, че подкрепяте развитието на CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Отвори страницата за спонсорство</string>
|
||||
<string name="settings_upgrade_status_label">Спонсорирайте CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Спонсорирайте CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Вашият статус на поддръжник.</string>
|
||||
<string name="upgrade_screen_why_title">Преимущества на надстройката</string>
|
||||
<string name="upgrade_screen_how_title">Как да помогнете</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">দান করুন</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">আমি ইতিমধ্যে দান করেছি</string>
|
||||
<string name="foss_upgrade_no_money_label">আমি আমার সব টাকা AirPods এর জন্য খরচ করি</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS বিনামূল্যে এবং মুক্ত উৎস। আপনি যদি এটি উপযোগী মনে করেন, প্রকল্পটি চালু রাখতে সাহায্য করতে ডেভেলপমেন্ট স্পনসর করার কথা বিবেচনা করুন।</string>
|
||||
<string name="upgrade_foss_sponsor_action">স্পনসর ডেভলপমেন্ট</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">কোনো বিজ্ঞাপন নেই। কোনো ট্র্যাকিং নেই। Google Play-এর সাথে বাঁধানো নেই।</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">%s থেকে সমর্থক</string>
|
||||
<string name="upgrade_foss_supporter_thanks">CAPod-এর উন্নয়নে সমর্থন করার জন্য আপনাকে ধন্যবাদ!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">স্পনসর পৃষ্ঠা খুলুন</string>
|
||||
<string name="settings_upgrade_status_label">CAPod-কে স্পন্সর করুন</string>
|
||||
<string name="upgrade_foss_sponsor_label">CAPod-কে স্পন্সর করুন</string>
|
||||
<string name="settings_upgrade_status_description">আপনার সমর্থক স্থিতি।</string>
|
||||
<string name="upgrade_screen_why_title">আপগ্রেড সুবিধা</string>
|
||||
<string name="upgrade_screen_how_title">কিভাবে সাহায্য করতে পারি</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Donacions</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Ja he donat</string>
|
||||
<string name="foss_upgrade_no_money_label">Gasto tots els meus diners en AirPods</string>
|
||||
<string name="upgrade_foss_preamble">El CAPod FOSS és gratuït i de codi obert. Si el trobeu útil, considereu patrocinar el desenvolupament per ajudar a mantenir el projecte en marxa.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Patrocina el desenvolupament</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Sense anuncis. Sense seguiment. Sense dependència del Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Col·laborador des de %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Gràcies per donar suport al desenvolupament del CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Obre la pàgina del patrocinador</string>
|
||||
<string name="settings_upgrade_status_label">Patrocineu el CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Patrocineu el CAPod</string>
|
||||
<string name="settings_upgrade_status_description">El vostre estat de col·laborador.</string>
|
||||
<string name="upgrade_screen_why_title">Beneficis de la millora</string>
|
||||
<string name="upgrade_screen_how_title">Com ajudar</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Přispět</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Již jsem přispěl/a</string>
|
||||
<string name="foss_upgrade_no_money_label">Všechny peníze jsem utratil/a za AirPods</string>
|
||||
<string name="upgrade_foss_preamble">Aplikace CAPod FOSS je zdarma a open source. Pokud vám přijde užitečná, zvažte sponzorování vývoje, abyste pomohli udržet projekt v chodu.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Sponzorovat vývoj</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Žádné reklamy. Žádné sledování. Žádná závislost na Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Podporovatel od %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Děkujeme za podporu vývoje CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Otevřít stránku sponzora</string>
|
||||
<string name="settings_upgrade_status_label">Sponzorovat CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Sponzorovat CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Váš stav podporovatele.</string>
|
||||
<string name="upgrade_screen_why_title">Výhody upgradu</string>
|
||||
<string name="upgrade_screen_how_title">Jak pomoci</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Doner</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Jeg har allerede doneret</string>
|
||||
<string name="foss_upgrade_no_money_label">Jeg bruger alle mine penge på AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS er gratis og open source. Hvis du finder det nüttigt, kan du overveje at sponsorere udviklingen for at hjælpe projektet med at fortsætte.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Sponsorudvikling</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Ingen annoncer. Ingen sporing. Ingen Google Play-lås.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Støtte siden %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Tak fordi du støtter udviklingen af CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Åbn sponsorside</string>
|
||||
<string name="settings_upgrade_status_label">Sponsor CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Sponsor CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Din supporterstatus.</string>
|
||||
<string name="upgrade_screen_why_title">Opgraderingens fordele</string>
|
||||
<string name="upgrade_screen_how_title">Sådan hjælper du</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Spenden</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Ich habe schon gespendet</string>
|
||||
<string name="foss_upgrade_no_money_label">Hab alles für AirPods ausgegeben</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS ist kostenlos und Open Source. Wenn du es nützlich findest, erwäge, die Entwicklung zu sponsern, um das Projekt am Laufen zu halten.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Fördere Entwicklung</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Keine Werbung. Kein Tracking. Keine Google-Play-Bindung.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Unterstützer seit %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Vielen Dank, dass du die Entwicklung von CAPod unterstützt!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Sponsoring-Seite öffnen</string>
|
||||
<string name="settings_upgrade_status_label">CAPod unterstützen</string>
|
||||
<string name="upgrade_foss_sponsor_label">CAPod unterstützen</string>
|
||||
<string name="settings_upgrade_status_description">Dein Unterstützerstatus.</string>
|
||||
<string name="upgrade_screen_why_title">Vorteile des Upgrades</string>
|
||||
<string name="upgrade_screen_how_title">Wie man hilft</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Δωρεά</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Ήδη δώρισα</string>
|
||||
<string name="foss_upgrade_no_money_label">Ξόδεψα όλα τα χρήματά μου στα AirPods</string>
|
||||
<string name="upgrade_foss_preamble">Το CAPod FOSS είναι δωρεάν και ανοιχτού κώδικα. Αν το βρίσκετε χρήσιμο, εξετάστε το ενδεχόμενο να χρηματοδοτήσετε την ανάπτυξη για να συνεχίσει το έργο.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Χορηγήστε την ανάπτυξη</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Χωρίς διαφημίσεις. Χωρίς παρακολούθηση. Χωρίς δέσμευση στο Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Υποστηρικτής από %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Σας ευχαριστούμε που υποστηρίζετε την ανάπτυξη του CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Άνοιγμα σελίδας χορηγίας</string>
|
||||
<string name="settings_upgrade_status_label">Χορηγήστε το CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Χορηγήστε το CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Η κατάστασή σας ως υποστηρικτή.</string>
|
||||
<string name="upgrade_screen_why_title">Πλεονεκτήματα αναβάθμισης</string>
|
||||
<string name="upgrade_screen_how_title">Πώς να βοηθήσετε</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Donar</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Ya doné</string>
|
||||
<string name="foss_upgrade_no_money_label">Gasto todo mi dinero en AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS es gratuito y de código abierto. Si lo encontrás útil, considerá patrocinar el desarrollo para ayudar a mantener el proyecto en marcha.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Sponsorear el desarrollo</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Sin publicidad. Sin rastreo. Sin dependencia de Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Colaborador desde %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">¡Gracias por apoyar el desarrollo de CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Abrir página de patrocinio</string>
|
||||
<string name="settings_upgrade_status_label">Apoyá CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Apoyá CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Tu estado de patrocinador.</string>
|
||||
<string name="upgrade_screen_why_title">Beneficios de la mejora</string>
|
||||
<string name="upgrade_screen_how_title">Como ayudar</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Donar</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Ya doné</string>
|
||||
<string name="foss_upgrade_no_money_label">Gasto todo mi dinero en AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS es gratuito y de código abierto. Si lo encuentras útil, considera patrocinar el desarrollo para ayudar a mantener el proyecto.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Patrocinar el desarrollo</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Sin anuncios. Sin rastreo. Sin dependencia de Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Colaborador desde %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">¡Gracias por apoyar el desarrollo de CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Abrir página de patrocinio</string>
|
||||
<string name="settings_upgrade_status_label">Patrocina CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Patrocina CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Tu estado de patrocinador.</string>
|
||||
<string name="upgrade_screen_why_title">Beneficios de la actualización</string>
|
||||
<string name="upgrade_screen_how_title">Cómo ayudar</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Donar</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Ya he donado</string>
|
||||
<string name="foss_upgrade_no_money_label">Gasto todo mi dinero en AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS es gratuito y de código abierto. Si lo encuentras útil, considera patrocinar el desarrollo para ayudar a mantener el proyecto en marcha.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Patrocinadores</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Sin anuncios. Sin rastreo. Sin dependencia de Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Colaborador desde %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">¡Gracias por apoyar el desarrollo de CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Abrir página de patrocinio</string>
|
||||
<string name="settings_upgrade_status_label">Patrocinar CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Patrocinar CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Tu estado como patrocinador.</string>
|
||||
<string name="upgrade_screen_why_title">Beneficios de la actualización</string>
|
||||
<string name="upgrade_screen_how_title">Como ayudar</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Anneta</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Juba annetasin</string>
|
||||
<string name="foss_upgrade_no_money_label">Kulutasin kogu raha AirPodsidele</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS on tasuta ja avatud lähtekoodiga. Kui sellest on sulle kasu, kaalu arenduse rahastamist, et aidata projekti arendada.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Rahasta arendamist</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Reklaami pole. Jälgimist pole. Google Play lukustust pole.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Toetaja alates %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Täname, et toetad CAPodi arendust!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Ava sponsorlehekülg</string>
|
||||
<string name="settings_upgrade_status_label">Rahasta CAPodi</string>
|
||||
<string name="upgrade_foss_sponsor_label">Rahasta CAPodi</string>
|
||||
<string name="settings_upgrade_status_description">Sinu toetaja staatus.</string>
|
||||
<string name="upgrade_screen_why_title">Uuendamise eelised</string>
|
||||
<string name="upgrade_screen_how_title">Kuidas aidata?</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Eman dohaintza</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Dagoeneko eman dut dohaintza</string>
|
||||
<string name="foss_upgrade_no_money_label">Nire diru guztia AirPods-etan gastatzen dut</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS doakoa eta kode irekikoa da. Erabilgarria iruditzen bazaizu, kontuan hartu garapena babestu proiektua aurrera jarraitzeko.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Garapena babestu</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Iragarkirik ez. Jarraipenik ez. Google Play-ren lotura ez.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Laguntzaile %s(e)tik aurrera</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Eskerrik asko CAPod-en garapena babesten duzulako!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Ireki babesle-orria</string>
|
||||
<string name="settings_upgrade_status_label">CAPod babeslatu</string>
|
||||
<string name="upgrade_foss_sponsor_label">CAPod babeslatu</string>
|
||||
<string name="settings_upgrade_status_description">Zure babesle egoera</string>
|
||||
<string name="upgrade_screen_why_title">Hobekuntza-onurak</string>
|
||||
<string name="upgrade_screen_how_title">Nola lagundu</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">کمک مالی</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">کمک مالی کردهام</string>
|
||||
<string name="foss_upgrade_no_money_label">من تمام پولم را خرج ایرپاد میکنم</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS رایگان و متن-باز است. اگر آن را مفید یافتید، حمایت مالی از توسعه را در نظر بگیرید تا پروژه ادامه یابد.</string>
|
||||
<string name="upgrade_foss_sponsor_action">حامی توسعه دهنده</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">بدون تبلیغ. بدون ردیابی. بدون وابستگی به Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">حامی از %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">از حمایت شما از توسعه CAPod سپاسگزاریم!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">باز کردن صفحه حمایت مالی</string>
|
||||
<string name="settings_upgrade_status_label">حمایت از CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">حمایت از CAPod</string>
|
||||
<string name="settings_upgrade_status_description">وضعیت حمایتی شما.</string>
|
||||
<string name="upgrade_screen_why_title">مزایای ارتقا</string>
|
||||
<string name="upgrade_screen_how_title">چگونه کمک کنیم</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Lahjoita</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Olen jo lahjoittanut</string>
|
||||
<string name="foss_upgrade_no_money_label">Käytän kaikki rahani AirPodeihin</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS on ilmainen ja avoimen lähdekoodin ohjelma. Jos löydät sen hyödylliseksi, harkitse kehityksen tukemista projektin elässä pitämiseksi.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Sponsoroi kehitystä</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Ei mainoksia. Ei seurantaa. Ei Google Play -riippuvuutta.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Tukija %s lähtien</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Kiitos, että tuet CAPodin kehitystä!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Avaa sponsorisivu</string>
|
||||
<string name="settings_upgrade_status_label">Tue CAPodia</string>
|
||||
<string name="upgrade_foss_sponsor_label">Tue CAPodia</string>
|
||||
<string name="settings_upgrade_status_description">Kannattajantila</string>
|
||||
<string name="upgrade_screen_why_title">Päivityksen edut</string>
|
||||
<string name="upgrade_screen_how_title">Kuinka auttaa</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Mag-donate</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Nakapag-donate na ako</string>
|
||||
<string name="foss_upgrade_no_money_label">Ginagastos ko lahat ng pera ko sa AirPods</string>
|
||||
<string name="upgrade_foss_preamble">Ang CAPod FOSS ay libre at open source. Kung nakita mong kapaki-pakinabang ito, isaalang-alang ang pag-sponsor ng development para mapanatiling aktibo ang proyekto.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Mag-sponsor ng development</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Walang ads. Walang tracking. Walang Google Play lock-in.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Tagasuporta simula %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Salamat sa pagsuporta sa development ng CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Buksan ang sponsor page</string>
|
||||
<string name="settings_upgrade_status_label">Suportahan ang CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Suportahan ang CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Ang iyong status bilang supporter.</string>
|
||||
<string name="upgrade_screen_why_title">Mga benepisyo ng upgrade</string>
|
||||
<string name="upgrade_screen_how_title">Paano tumulong</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Faire un don</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">J’ai déjà fait un don</string>
|
||||
<string name="foss_upgrade_no_money_label">J’ai dépensé tout mon argent sur des AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS est gratuite et à code source ouvert. Si vous la trouvez utile, pensez à soutenir le développement pour contribuer à la pérennité du projet.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Soutenir le développement</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Pas de publicités. Pas de suivi à la trace. Pas de dépendance à Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Soutien depuis %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Merci de soutenir le développement de CAPod !</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Ouvrir la page de parrainage</string>
|
||||
<string name="settings_upgrade_status_label">Soutenir CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Soutenir CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Votre statut de supporter.</string>
|
||||
<string name="upgrade_screen_why_title">Avantages de la mise à niveau</string>
|
||||
<string name="upgrade_screen_how_title">Comment aider</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Doar</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Xa doei</string>
|
||||
<string name="foss_upgrade_no_money_label">Gasto todos os meus cartos en AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS é gratuito e de código aberto. Se o atopas útil, considera patrocinar o desenvolvemento para axudar a manter o proxecto en marcha.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Patrocinar desenvolvemento</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Sen anuncios. Sen rastrexo. Sen dependencia de Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Colaborador desde %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Grazas por apoiar o desenvolvemento de CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Abrir a páxina de patrocinio</string>
|
||||
<string name="settings_upgrade_status_label">Apoia CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Apoia CAPod</string>
|
||||
<string name="settings_upgrade_status_description">O teu estado de apoiador.</string>
|
||||
<string name="upgrade_screen_why_title">Vantaxes da actualización</string>
|
||||
<string name="upgrade_screen_how_title">Como axudar</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">दान करें</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">मैंने पहले ही दान कर दिया है</string>
|
||||
<string name="foss_upgrade_no_money_label">मैं अपना सारा पैसा AirPods पर खर्च करता हूं</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS मुफ्त और ओपन सोर्स है। यदि आपको यह उपयोगी लगता है, तो प्रोजेक्ट को जारी रखने में मदद के लिए विकास को प्रायोजित करने पर विचार करें।</string>
|
||||
<string name="upgrade_foss_sponsor_action">विकास को प्रायोजक करें</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">कोई विज्ञापन नहीं। कोई ट्रैकिंग नहीं। Google Play पर निर्भरता नहीं।</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">%s से समर्थक</string>
|
||||
<string name="upgrade_foss_supporter_thanks">CAPod के विकास का समर्थन करने के लिए धन्यवाद!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">स्पॉन्सर पेज खोलें</string>
|
||||
<string name="settings_upgrade_status_label">CAPod को प्रायोजित करें</string>
|
||||
<string name="upgrade_foss_sponsor_label">CAPod को प्रायोजित करें</string>
|
||||
<string name="settings_upgrade_status_description">आपकी समर्थक स्थिति।</string>
|
||||
<string name="upgrade_screen_why_title">अपग्रेड के लाभ</string>
|
||||
<string name="upgrade_screen_how_title">कैसे सहायता करें</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Donirajte</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Već sam donirao</string>
|
||||
<string name="foss_upgrade_no_money_label">Sav svoj novac trošim na AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS je besplatan i otvorenog koda. Ako vam je koristan, razmislite o sponzoriranju razvoja kako bi projekt ostao živ.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Podržite razvoj</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Bez oglasa. Bez praćenja. Bez zaključanosti na Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Podržavatelj od %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Hvala vam što podržavate razvoj CAPoda!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Otvori stranicu za sponzoriranje</string>
|
||||
<string name="settings_upgrade_status_label">Sponzorirajte CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Sponzorirajte CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Vaš status sponzora</string>
|
||||
<string name="upgrade_screen_why_title">Prednosti nadogradnje</string>
|
||||
<string name="upgrade_screen_how_title">Kako pomoći</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Adományoz</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Már adományoztam</string>
|
||||
<string name="foss_upgrade_no_money_label">Minden pénzemet az AirPods-ra költöttem</string>
|
||||
<string name="upgrade_foss_preamble">A CAPod FOSS ingyenes és nyílt forráskódú. Ha hasznosnak találod, fontold meg a fejlesztés támogatását, hogy a projekt tovább élhessen.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Szponzori fejlesztés</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Nincs hirdetés. Nincs nyomkövetés. Nincs Google Play-függőség.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Támogató %s óta</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Köszönjük, hogy támogatod a CAPod fejlesztését!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Támogatói oldal megnyitása</string>
|
||||
<string name="settings_upgrade_status_label">Támogasson CAPod-ot</string>
|
||||
<string name="upgrade_foss_sponsor_label">Támogasson CAPod-ot</string>
|
||||
<string name="settings_upgrade_status_description">Az Ön támogató státusza.</string>
|
||||
<string name="upgrade_screen_why_title">Frissítési előnyök</string>
|
||||
<string name="upgrade_screen_how_title">Hogyan lehet segíteni</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Նվիրաբերել</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Ես արդեն նվիրաբերել եմ</string>
|
||||
<string name="foss_upgrade_no_money_label">Ես իմ ամբողջ գումարը ծախսում եմ AirPods-ի վրա</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS անվճար և բաց կոդով: Այն ոգտակար էք, խնդրում զարգացությունը կենդանի պահելու համար:</string>
|
||||
<string name="upgrade_foss_sponsor_action">Հովանավորել զարգացումը</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Լրատնություն չկա: հետքխկություն չկա: Google Play-ի կապվածություն չկա:</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Հովանավոր է %s-ից</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Շնորհակալություն CAPod-ի զարգացումն աջակցելու համար!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Բացել հովանավորության էջը</string>
|
||||
<string name="settings_upgrade_status_label">Աջակեք CAPod-ին</string>
|
||||
<string name="upgrade_foss_sponsor_label">Աջակեք CAPod-ին</string>
|
||||
<string name="settings_upgrade_status_description">Ձեր աջակցողի վիճակը:</string>
|
||||
<string name="upgrade_screen_why_title">Թարմացման առավելությունները</string>
|
||||
<string name="upgrade_screen_how_title">Ինչպես օգնել</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Menyumbang</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Saya telah berdonasi</string>
|
||||
<string name="foss_upgrade_no_money_label">Saya menghabiskan semua uang saya untuk AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS gratis dan sumber terbuka. Jika Anda merasa bermanfaat, pertimbangkan untuk mensponsori pengembangan guna menjaga proyek ini berjalan.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Pengembangan sponsor</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Tanpa iklan. Tanpa pelacakan. Tanpa ketergantungan Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Pendukung sejak %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Terima kasih karena telah mendukung pengembangan CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Buka halaman sponsor</string>
|
||||
<string name="settings_upgrade_status_label">Sponsori CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Sponsori CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Status pendukung Anda.</string>
|
||||
<string name="upgrade_screen_why_title">Manfaat upgrade</string>
|
||||
<string name="upgrade_screen_how_title">Cara membantu</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Gefa</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Ég hef þegar gefið</string>
|
||||
<string name="foss_upgrade_no_money_label">Ég eyði öllum peningunum mínum í AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS er ókeypis og opinn kóði. Ef þú finnur það gagnlegt, þakkaðu þér fyrir að stuðla að þróun til að hjálpa til við að halda verkefninu gangandi.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Styrkja þróun</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Engar auglysíngar. Engin rakning. Ekkert Google Play-lás.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Styrktaraðili síðan %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Takk fyrir að styðja þróun CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Opna styrktarsíðu</string>
|
||||
<string name="settings_upgrade_status_label">Styrktu CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Styrktu CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Staða þín sem stuðningsmaður</string>
|
||||
<string name="upgrade_screen_why_title">Uppfærslukostir</string>
|
||||
<string name="upgrade_screen_how_title">Hvernig á að hjálpa</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Dona</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Ho già donato</string>
|
||||
<string name="foss_upgrade_no_money_label">Spendo tutti i miei soldi per gli AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS è gratuito e open source. Se lo trovi utile, considera di sponsorizzare lo sviluppo per contribuire a mantenere il progetto in vita.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Finanzia lo sviluppo</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Nessuna pubblicità. Nessun tracciamento. Nessun vincolo con Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Sostenitore dal %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Grazie per supportare lo sviluppo di CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Apri la pagina sponsor</string>
|
||||
<string name="settings_upgrade_status_label">Sponsorizza CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Sponsorizza CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Il tuo status di sostenitore.</string>
|
||||
<string name="upgrade_screen_why_title">Vantaggi dell\'aggiornamento</string>
|
||||
<string name="upgrade_screen_how_title">Come aiutare</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">תרומה</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">כבר תרמתי</string>
|
||||
<string name="foss_upgrade_no_money_label">מיטב כספי מושקע ב-AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS הוא חינם וקוד פתוח. אם הוא שימושי לך, שקול לשקול על חסוי הפיתוח כדי לעזור להמשיך את הפרויקט.</string>
|
||||
<string name="upgrade_foss_sponsor_action">פיתוח חסות</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">אין פרסומות. אין מעקב. אין תלות ב-Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">תומך מאז %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">תודה שאתה תומך בפיתוח של CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">פתח את דף התמיכה</string>
|
||||
<string name="settings_upgrade_status_label">תמוך ב-CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">תמוך ב-CAPod</string>
|
||||
<string name="settings_upgrade_status_description">סטטוס התומך שלך.</string>
|
||||
<string name="upgrade_screen_why_title">יתרונות השדרוג</string>
|
||||
<string name="upgrade_screen_how_title">איך לעזור</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">寄付</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">すでに寄付しました</string>
|
||||
<string name="foss_upgrade_no_money_label">AirPodsに全財産をつぎ込んでいます</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSSは無料かつオープンソースです。便利だと思ったら、プロジェクトの継続に向けて開発をスポンサーすることをご検討ください。</string>
|
||||
<string name="upgrade_foss_sponsor_action">開発支援者</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">広告なし。追跡なし。Google Play依存なし。</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">%sからのサポーター</string>
|
||||
<string name="upgrade_foss_supporter_thanks">CAPodの開発を支援していただきありがとうございます!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">スポンサーページを開く</string>
|
||||
<string name="settings_upgrade_status_label">CAPod をスポンサーする</string>
|
||||
<string name="upgrade_foss_sponsor_label">CAPod をスポンサーする</string>
|
||||
<string name="settings_upgrade_status_description">サポーターステータス。</string>
|
||||
<string name="upgrade_screen_why_title">アップグレードの特典</string>
|
||||
<string name="upgrade_screen_how_title">協力するには</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">შემოწირულობა</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">მე უკვე შემოწირული მაქვს</string>
|
||||
<string name="foss_upgrade_no_money_label">მთელ ჩემს ფულს AirPods-ებზე ვხარჯავ</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS უფასოა და ღია წყაროა. თუ გსარგებლია, გაანაბრეთ განვითარება პროექტის გადასაგრელად.</string>
|
||||
<string name="upgrade_foss_sponsor_action">სპონსორის განვითარება</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">რეკლამა არა. თვალთვალება არა. Google Play-ზე დამოკიდება არა.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">მხარდამჭერი %s-დან</string>
|
||||
<string name="upgrade_foss_supporter_thanks">მადლობა CAPod-ის განვითარების მხარდაჭერისთვის!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">სპონსორის გვერდის გახსნა</string>
|
||||
<string name="settings_upgrade_status_label">მხარი დაუჭიროთ CAPod-ს</string>
|
||||
<string name="upgrade_foss_sponsor_label">მხარი დაუჭიროთ CAPod-ს</string>
|
||||
<string name="settings_upgrade_status_description">თქვენი მხარდამჭერის მდგომარეობა.</string>
|
||||
<string name="upgrade_screen_why_title">განახლების უპირატესობები</string>
|
||||
<string name="upgrade_screen_how_title">როგორ დაგეხმაროთ</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">បរិច្ចាគ</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">ខ្ញុំបានបរិច្ចាគរួចហើយ</string>
|
||||
<string name="foss_upgrade_no_money_label">ខ្ញុំចំណាយលុយទាំងអស់របស់ខ្ញុំលើ AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS ជាកម្មវិធីឥតគិតថ្លៃ និងប្រភពបើកចំហ។ ប្រសិនបើអ្នករកហើញថ្លៃវាមានប្រយោជន៍ សូមពិចារណាឧបត្ថម្ភការអភិវឌ្ឍន៍ ដើម្បីជួយឥ្សរិយគម្រោងបន្តដំណើរការ។</string>
|
||||
<string name="upgrade_foss_sponsor_action">ឧបត្ថម្ភការអភិវឌ្ឍន៍</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">គ្មានការផ្សាយពាណិជ្ជកម្ម។ គ្មានការតាមដាន។ គ្មានការចាក់សោ Google Play។</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">អ្នកគាំទ្រតាំងពី %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">សូមអរគុណសម្រាប់ការគាំទ្រការអភិវឌ្ឍន៍ CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">បើកទំព័រឧបត្ថម្ភ</string>
|
||||
<string name="settings_upgrade_status_label">ឧបត្ថម្ភ CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">ឧបត្ថម្ភ CAPod</string>
|
||||
<string name="settings_upgrade_status_description">ស្ថានភាពអ្នកគាំទ្ររបស់អ្នក។</string>
|
||||
<string name="upgrade_screen_why_title">អត្ថប្រយោជន៍លើកកម្ពស់</string>
|
||||
<string name="upgrade_screen_how_title">របៀបជួយ</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Beşdar bike</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Min berê bexş kir</string>
|
||||
<string name="foss_upgrade_no_money_label">Ez hemû pereyê xwe li AirPods xerc dikim</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS belaş û çavkaniya vekirî ye. Ger hûn wê kêrhastî dibînin, fikir bikin ku perkirezan bike da ku alîkariya berdewamî projeya bide.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Pêşdebiriyê teref bike</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Reklam tune. Śopandin tune. Girêdana Google Play tune.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Piştgir ji %s ve</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Spas ji bo piştgiriya te ya pêşxistina CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Rûpela piştgiriyê veke</string>
|
||||
<string name="settings_upgrade_status_label">CAPod-ê piştgirî bike</string>
|
||||
<string name="upgrade_foss_sponsor_label">CAPod-ê piştgirî bike</string>
|
||||
<string name="settings_upgrade_status_description">Rewşa piştgiriya te.</string>
|
||||
<string name="upgrade_screen_why_title">Feydeyên Bardiyê</string>
|
||||
<string name="upgrade_screen_how_title">Çawa Alîkarî Bike</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">ದಾನ ಮಾಡಿ</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">ನಾನು ಈಗಾಗಲೇ ದಾನ ಮಾಡಿದ್ದೇನೆ</string>
|
||||
<string name="foss_upgrade_no_money_label">ನಾನು ನನ್ನ ಎಲ್ಲಾ ಹಣವನ್ನು AirPods ಗಾಗಿ ಖರ್ಚು ಮಾಡುತ್ತೇನೆ</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS ಉಚಿತ ಮತ್ತು ತೆರೆದ ಮೂಲದ್ದಾಗಿದೆ. ನಿಮಗೆ ಉಪಯುಕ್ತವಾಗಿ ತೋರಿದರೆ, ಯೋಜನೆಯನ್ನು ಮುಂದುವರಿಸಲು ಅಭಿವೃದ್ಧಿಗೆ ಪ್ರಾಯೋಜಕತ್ವ ನೀಡಲು ಪರಿಗಣಿಸಿ.</string>
|
||||
<string name="upgrade_foss_sponsor_action">ಅಭಿವೃದ್ಧಿಗೆ ಪ್ರಾಯೋಜಕತ್ವ ನೀಡಿ</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">ಜಾಹೀರಾತುಗಳಿಲ್ಲ. ಭಕ್ಷಣೆ ಇಲ್ಲ. Google Play ಲಾಕ್-ಇನ್ ಇಲ್ಲ.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">%s ರಿಂದ ಬೆಂಬಲಿಗ</string>
|
||||
<string name="upgrade_foss_supporter_thanks">CAPod ಅಭಿವೃದ್ಧಿಗೆ ಬೆಂಬಲ ನೀಡಿದ್ದಕ್ಕಾಗಿ ಧನ್ಯವಾದಗಳು!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">ಪ್ರಾಯೋಜಕ ಪುಟ ತೆರೆಯಿರಿ</string>
|
||||
<string name="settings_upgrade_status_label">CAPod ಬೆಂಬಲಿಸಿ</string>
|
||||
<string name="upgrade_foss_sponsor_label">CAPod ಬೆಂಬಲಿಸಿ</string>
|
||||
<string name="settings_upgrade_status_description">ನಿಮ್ಮ ಬೆಂಬಲಕಾರ ಸ್ಥಿತಿ.</string>
|
||||
<string name="upgrade_screen_why_title">ನವೀಕರಣ ಪ್ರಯೋಜನಗಳು</string>
|
||||
<string name="upgrade_screen_how_title">ಸಹಾಯ ಮಾಡುವುದು ಹೇಗೆ</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">후원하기</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">이미 후원했어요</string>
|
||||
<string name="foss_upgrade_no_money_label">AirPods에 돈을 다 썼어요</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS는 무료 오픈소스입니다. 유용하게 사용하고 계신다면 프로젝트 유지에 도움이 되도록 후원을 고려해 주세요.</string>
|
||||
<string name="upgrade_foss_sponsor_action">개발 후원하기</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">광고 없음. 추적 없음. Google Play 의존 없음.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">%s부터 후원자</string>
|
||||
<string name="upgrade_foss_supporter_thanks">CAPod 개발을 후원해 주셔서 감사합니다!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">후원 페이지 열기</string>
|
||||
<string name="settings_upgrade_status_label">CAPod 후원하기</string>
|
||||
<string name="upgrade_foss_sponsor_label">CAPod 후원하기</string>
|
||||
<string name="settings_upgrade_status_description">후원자 상태</string>
|
||||
<string name="upgrade_screen_why_title">업그레이드 혜택</string>
|
||||
<string name="upgrade_screen_how_title">도움 주기</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Демөөрчүлүк кылуу</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Мен буга чейин демөөрчүлүк кылгам</string>
|
||||
<string name="foss_upgrade_no_money_label">Мен бүт акчамды AirPods үчүн короттум</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS беплатан жана ачык каттулууда. Егер пайдалуу таапсаңыз, жобаны улантырууга колдоо көрсөтүңүз.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Иштелеп чыгууну колдоо</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Жарнама. Кадалоолоо жок. Google Playга байланма.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">%s\'дан бери колдоочу</string>
|
||||
<string name="upgrade_foss_supporter_thanks">CAPod\'дун өнүгүшүн колдогонуңуз үчүн рахмат!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Демөөрчүлүк баракчасын ачуу</string>
|
||||
<string name="settings_upgrade_status_label">CAPod-ды спонсорлоо</string>
|
||||
<string name="upgrade_foss_sponsor_label">CAPod-ды спонсорлоо</string>
|
||||
<string name="settings_upgrade_status_description">Колдоочу абалы</string>
|
||||
<string name="upgrade_screen_why_title">Жогорулоонун пайдалары</string>
|
||||
<string name="upgrade_screen_how_title">Жардам бергүүнүн жолу</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">ບໍລິຈາກ</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">ຂ້ອຍໄດ້ບໍລິຈາກແລ້ວ</string>
|
||||
<string name="foss_upgrade_no_money_label">ຂ້ອຍໃຊ້ເງິນທັງໝົດຂອງຂ້ອຍໃສ່ AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS ແມ່ນຟຣີ ແລະ ໂອເພນຊອດ. ຖ້າທ່ານເຫັນວ່າມີປະໂຫຍດ, ພິຈາລະນາສະໜັບສະໜູນການພັດທະນາເພື່ອຊ່ວຍໃຫ້ໂຄງການດຳເນີນຕໍ່ໄປ.</string>
|
||||
<string name="upgrade_foss_sponsor_action">ສະໜັບສະໜູນການພັດທະນາ</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">ບໍ່ມີໂຄສະນາ. ບໍ່ມີການຕິດຕາມ. ບໍ່ຕິດ Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">ຜູ້ສະໜັບສະໜູນຕັ້ງແຕ່ %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">ຂອບຊູ່ສຳລັບການສະໜັບສະໜູນການພັດທະນາຂອງ CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">ເປິດໝ້າສະໜັບສະໜູນ</string>
|
||||
<string name="settings_upgrade_status_label">ສະໜັບສະໜູນ CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">ສະໜັບສະໜູນ CAPod</string>
|
||||
<string name="settings_upgrade_status_description">ສະຖານະຜູ້ສະໜັບສະໜູນຂອງທ່ານ</string>
|
||||
<string name="upgrade_screen_why_title">ປະໂຫຍດຂອງການອັບເກຣດ</string>
|
||||
<string name="upgrade_screen_how_title">ວິທີການຊ່ວຍເຫຼືອ</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Aukoti</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Aš jau paaukojau</string>
|
||||
<string name="foss_upgrade_no_money_label">Visus savo pinigus išleidžiu „AirPods“ ausinėms</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS yra nemokama ir atviro kodo programa. Jei ji jums naudinga, pagalvokite apie rėmimą, kad projektą būtų galima tęsti.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Remti plėtrą</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Jokių reklamų. Jokio sekimo. Jokio „Google Play“ priklausomybės.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Rėmėjas nuo %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Ačiū, kad remiate CAPod kūrimą!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Atidaryti rėmėjo puslapį</string>
|
||||
<string name="settings_upgrade_status_label">Remti CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Remti CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Jūsų rėmėjo statusas.</string>
|
||||
<string name="upgrade_screen_why_title">Atnaujinimo privalumai</string>
|
||||
<string name="upgrade_screen_how_title">Kaip padėti</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Ziedot</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Es jau ziedoju</string>
|
||||
<string name="foss_upgrade_no_money_label">Es iztērēju visu savu naudu AirPods austiņām</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS ir bezmaksas un atkļējkoda. Ja tā ir noderba, apsveriet attīstības sponsēšanu, lai palīdzētu turpināt projektu.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Sponsorēt attīstību</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Bez reklamām. Bez izsekošanas. Bez Google Play saistībām.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Atbalstītājs kopš %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Paldies, ka atbalsti CAPod izstrādi!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Atvērt sponsorēšanas lapu</string>
|
||||
<string name="settings_upgrade_status_label">Atbalstīt CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Atbalstīt CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Jūsu atbalstītāja statuss.</string>
|
||||
<string name="upgrade_screen_why_title">Jaunināšanas priekšrocības</string>
|
||||
<string name="upgrade_screen_how_title">Kā palīdzēt</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Донирај</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Веќе донирав</string>
|
||||
<string name="foss_upgrade_no_money_label">Ги трошам сите пари на AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS е бесплатен и отворен код. Ако ви е корисен, размислете да го спонзорирате развојот за да помогнете проектот да продолжи.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Спонзорирај развој</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Без реклами. Без пратење. Без зависност од Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Поддржувач од %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Ти благодариме што го поддржуваш развојот на CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Отвори ја страницата за спонзорство</string>
|
||||
<string name="settings_upgrade_status_label">Поддржи CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Поддржи CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Твојот статус на поддржувач.</string>
|
||||
<string name="upgrade_screen_why_title">Предности на надградување</string>
|
||||
<string name="upgrade_screen_how_title">Како да помогнете</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">സംഭാവന ചെയ്യുക</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">ഞാൻ ഇതിനകം സംഭാവന ചെയ്തു</string>
|
||||
<string name="foss_upgrade_no_money_label">ഞാൻ എൻ്റെ എല്ലാ പണവും AirPods-നായി ചെലവഴിക്കുന്നു</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS സൗജന്യവും ഓപ്പൻ സോഴ്സുമാണ്. നിങ്ങൾക്ക് ഇത് ഉപകാരപ്രദമാണെങ്കിൽ, പ്രോജെക്ട് തുടരാൻ സഹായിക്കുന്നതിന് ഡെവലപ്പ്മെന്റ് സ്പോൺസർ ചെയ്യുന്നത് പരിഗണിക്കുക.</string>
|
||||
<string name="upgrade_foss_sponsor_action">വികസനം സ്പോൺസർ ചെയ്യുക</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">പരസ്യങ്ങൾ ഇല്ല. ട്രാക്കിംഗ് ഇല്ല. Google Play ലോക്ക്-ഇൻ ഇല്ല.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">%s മുതൽ പിന്തുണക്കാൻ</string>
|
||||
<string name="upgrade_foss_supporter_thanks">CAPod-ന്റെ വികസനത്തെ പിന്തുണച്ചതിന് നന്ദി!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">സ്പോൺസർ പേജ് തുറക്കുക</string>
|
||||
<string name="settings_upgrade_status_label">CAPod നെ സ്പോൺസർ ചെയ്യുക</string>
|
||||
<string name="upgrade_foss_sponsor_label">CAPod നെ സ്പോൺസർ ചെയ്യുക</string>
|
||||
<string name="settings_upgrade_status_description">നിങ്ങളുടെ പിന്തുണയ സ്ഥിതി.</string>
|
||||
<string name="upgrade_screen_why_title">അപ്ഗ്രേഡ് നേട്ടങ്ങൾ</string>
|
||||
<string name="upgrade_screen_how_title">എങ്ങനെ സഹായിക്കാം</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Хандивлах</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Би аль хэдийн хандивласан</string>
|
||||
<string name="foss_upgrade_no_money_label">Би бүх мөнгөө AirPods-д зарцуулдаг</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS нь үнэгүй, нээлтэй эхийн програм. Хэрэв танд хэрэгтэй бол төслийг үргэлжүүлэхэд туслахын тулд хөгжүүлэлтийг дэмжих талаар бодоорой.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Хөгжлийг дэмжих</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Реклам байхгүй. Хяналт байхгүй. Google Play-д хамааралгүй.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">%s-с хойш дэмжигч</string>
|
||||
<string name="upgrade_foss_supporter_thanks">CAPod-ын хөгжлийг дэмжсэнд баярлалаа!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Дэмжигчийн хуудсыг нээх</string>
|
||||
<string name="settings_upgrade_status_label">CAPod-ыг спонсорлох</string>
|
||||
<string name="upgrade_foss_sponsor_label">CAPod-ыг спонсорлох</string>
|
||||
<string name="settings_upgrade_status_description">Таны дэмжигч статус.</string>
|
||||
<string name="upgrade_screen_why_title">Давуу талууд</string>
|
||||
<string name="upgrade_screen_how_title">Хэрхэн тусламж үзүүлэх</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">दान करा</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">मी आधीच दान केले आहे</string>
|
||||
<string name="foss_upgrade_no_money_label">मी माझे सर्व पैसे AirPods वर खर्च करतो</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS विनामूल्य आणि ओपन सोर्स आहे. जर तुम्हाला ते उपयुक्त वाटत असेल, तर प्रकल्प सुरू ठेवण्यासाठी विकासास प्रायोजित करण्याचा विचार करा.</string>
|
||||
<string name="upgrade_foss_sponsor_action">विकासास प्रायोजित करा</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">कोणत्याही जाहिराती नाहीत. कोणतेही ट्रॅकिंग नाही. Google Play चे बंधन नाही.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">%s पासून समर्थक</string>
|
||||
<string name="upgrade_foss_supporter_thanks">CAPod च्या विकासाला पाठिंबा दिल्याबद्दल धन्यवाद!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">प्रायोजक पृष्ठ उघडा</string>
|
||||
<string name="settings_upgrade_status_label">CAPod ला प्रायोजित करा</string>
|
||||
<string name="upgrade_foss_sponsor_label">CAPod ला प्रायोजित करा</string>
|
||||
<string name="settings_upgrade_status_description">आपल्या समर्थक स्थिती.</string>
|
||||
<string name="upgrade_screen_why_title">अपग्रेड लाभ</string>
|
||||
<string name="upgrade_screen_how_title">मदत कसे करायची</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Sumbang</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Saya telah menyumbang</string>
|
||||
<string name="foss_upgrade_no_money_label">Saya belanjakan semua wang saya untuk AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS adalah percuma dan sumber terbuka. Jika anda mendapatinya berguna, pertimbangkan untuk menaja pembangunannya bagi membantu projek ini terus berjalan.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Pembangunan penaja</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Tiada iklan. Tiada penjejakan. Tiada ikatan Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Penyokong sejak %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Terima kasih kerana menyokong pembangunan CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Buka halaman penaja</string>
|
||||
<string name="settings_upgrade_status_label">Menajakan CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Menajakan CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Status penyokong anda.</string>
|
||||
<string name="upgrade_screen_why_title">Manfaat peningkatan</string>
|
||||
<string name="upgrade_screen_how_title">Bagaimana untuk membantu</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">လှူဒါန်းပါ</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">ကျွန်တော် လှူပြီးပါပြီ</string>
|
||||
<string name="foss_upgrade_no_money_label">ကျွန်တော် ပိုက်ဆံအားလုံးကို AirPods မှာ သုံးတယ်</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS သည် အခမဲ့နှင့် open source ဖြစ်သည်။ အသုံးပြုခုပ်ဆည်းနိုင် အသားဖြစ်ပါက ပရာဗက်ဆက်လက်ရန် ဖွဲ့ဖြီးရေးကို ပံ့ပိုးပေးသည်။</string>
|
||||
<string name="upgrade_foss_sponsor_action">ဖွံ့ဖြိုးတိုးတက်မှုကို ကမကထပံ့ပိုးရန်</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">ကြောင်ညာမရှိ။ ခြေရာမခံ။ Google Play ချုပ်ချယ်မြုမရှိ။</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">%s မှစ၍ ပံ့ပိုးသူ</string>
|
||||
<string name="upgrade_foss_supporter_thanks">CAPod ၏ ဖွံ့ဖြိုးတိုးတက်မှုကို ပံ့ပိုးပေးသည့်အတွက် ကျေးဇူးတင်ပါသည်!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">ပံ့ပိုးသူစာမျက်နှာကို ဖွင့်ပါ</string>
|
||||
<string name="settings_upgrade_status_label">CAPod ကို ပံ့ပိုးပါ</string>
|
||||
<string name="upgrade_foss_sponsor_label">CAPod ကို ပံ့ပိုးပါ</string>
|
||||
<string name="settings_upgrade_status_description">သင်၏ ပံ့ပိုးသူ အခြေအနေ။</string>
|
||||
<string name="upgrade_screen_why_title">အဆင့်မြှင့်တင်မှု အကျိုးအကျေးမများ</string>
|
||||
<string name="upgrade_screen_how_title">ကူညီနည်းလမ်း</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Doner</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Jeg har allerede donert</string>
|
||||
<string name="foss_upgrade_no_money_label">Jeg bruker alle pengene mine på AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS er gratis og åpen kildekode. Hvis du synes den er nyttig, kan du vurdere å sponse utviklingen for å bidra til at prosjektet går videre.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Støtt utviklingen</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Ingen reklame. Ingen sporing. Ingen Google Play-avhengighet.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Støttespiller siden %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Takk for at du støtter utviklingen av CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Åpne sponsorside</string>
|
||||
<string name="settings_upgrade_status_label">Støtt CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Støtt CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Din status som støtter.</string>
|
||||
<string name="upgrade_screen_why_title">Oppgraderingsfordeler</string>
|
||||
<string name="upgrade_screen_how_title">Hvordan hjelpe</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">दान गर्नुहोस्</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">मैले पहिले नै दान गरिसकेको छु</string>
|
||||
<string name="foss_upgrade_no_money_label">म आफ्नो सबै पैसा AirPods मा खर्च गर्छु</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS निःशुल्क र खुला स्रोत छ। यदि तपाईंले उपयोगी पाउनुभएको छ भने, प्राजेक्ट चलाउन राख्न विकासमा प्रायोजन गर्न विचार गर्नुहोस्।</string>
|
||||
<string name="upgrade_foss_sponsor_action">विकासलाई प्रायोजन गर्नुहोस्</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">कुनै विज्ञापन छैन। कुनै ट्रैकिङ छैन। Google Playमा बाँधिएको छैन।</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">%s देखि समर्थक</string>
|
||||
<string name="upgrade_foss_supporter_thanks">CAPod को विकासलाई समर्थन गर्नुभएकोमा धन्यवाद!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">प्रायोजक पृष्ठ खोल्नुहोस्</string>
|
||||
<string name="settings_upgrade_status_label">CAPod को प्रायोजन गर्नुहोस्</string>
|
||||
<string name="upgrade_foss_sponsor_label">CAPod को प्रायोजन गर्नुहोस्</string>
|
||||
<string name="settings_upgrade_status_description">आफ्नो समर्थक स्थिति।</string>
|
||||
<string name="upgrade_screen_why_title">अपग्रेड लाभहरु</string>
|
||||
<string name="upgrade_screen_how_title">कसरी मद्दत गर्ने</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Doneren</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Ik heb al gedoneerd</string>
|
||||
<string name="foss_upgrade_no_money_label">Ik besteed al mijn geld aan AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS is gratis en open source. Als je het nuttig vindt, overweeg dan om de ontwikkeling te sponsoren om het project gaande te houden.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Sponsor ontwikkeling</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Geen advertenties. Geen tracking. Geen Google Play-vergrendeling.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Supporter sinds %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Bedankt voor je steun aan de ontwikkeling van CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Open de sponsorpagina</string>
|
||||
<string name="settings_upgrade_status_label">Sponsor CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Sponsor CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Je ondersteunersstatus.</string>
|
||||
<string name="upgrade_screen_why_title">Voordelen van de upgrade</string>
|
||||
<string name="upgrade_screen_how_title">Hoe kun je helpen</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Wesprzyj</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Już wsparłem</string>
|
||||
<string name="foss_upgrade_no_money_label">Wydałem wszystkie pieniądze na AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod Foss jest bezpłatny i otwarto-źródłowy. Jeśli uważasz go za użyteczny, rozważ sponsorowanie rozwoju, aby pomóc kontynuować projekt.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Wesprzyj rozwój</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Bez reklam. Bez śledzenia. Bez uzależnienia od Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Wspierający od %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Dziękuję za wsparcie rozwoju CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Otwórz stronę wspierającego</string>
|
||||
<string name="settings_upgrade_status_label">Sponsoruj CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Sponsoruj CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Status twojego wsparcia.</string>
|
||||
<string name="upgrade_screen_why_title">Korzyści z uaktualnienia</string>
|
||||
<string name="upgrade_screen_how_title">Jak pomóc</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Doar</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Eu já doei</string>
|
||||
<string name="foss_upgrade_no_money_label">Eu gasto todo o meu dinheiro em AirPods</string>
|
||||
<string name="upgrade_foss_preamble">O CAPod FOSS é gratuito e de código aberto. Se você o achar útil, considere patrocinar o desenvolvimento para ajudar a manter o projeto ativo.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Patrocinador do desenvolvimento</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Sem anúncios. Sem rastreamento. Sem dependência do Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Apoiador desde %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Obrigado por apoiar o desenvolvimento do CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Abrir página de patrocínio</string>
|
||||
<string name="settings_upgrade_status_label">Patrocine o CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Patrocine o CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Seu status de apoiador.</string>
|
||||
<string name="upgrade_screen_why_title">Benefícios da atualização</string>
|
||||
<string name="upgrade_screen_how_title">Como ajudar</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Doar</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Já doei</string>
|
||||
<string name="foss_upgrade_no_money_label">Gastei todo o meu dinheiro em AirPods</string>
|
||||
<string name="upgrade_foss_preamble">O CAPod FOSS é gratuito e de código aberto. Se o achar útil, considere patrocinar o desenvolvimento para ajudar a manter o projeto ativo.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Patrocine o desenvolvimento</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Sem anúncios. Sem rastreamento. Sem dependência do Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Apoiante desde %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Obrigado por apoiares o desenvolvimento do CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Abrir página de patrocínio</string>
|
||||
<string name="settings_upgrade_status_label">Apoie o CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Apoie o CAPod</string>
|
||||
<string name="settings_upgrade_status_description">O seu estado de apoiante.</string>
|
||||
<string name="upgrade_screen_why_title">Benefícios da atualização</string>
|
||||
<string name="upgrade_screen_how_title">Como ajudar</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Donar</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Hai gia donat</string>
|
||||
<string name="foss_upgrade_no_money_label">Mett tut mes daners en AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS è gratuit ed open source. Sche ti chattast ch\'el è util, considerescha da sponsurisar il sviluppament per agidà a mantegnair il project.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Sponsurisar il sviluppament</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Nagins annunzis. Nagin tracking. Nagin blocadi da Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Sustegnider dapi %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Grazia per sustegnair il svilup da CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Avrir la pagina da sponsurisaziun</string>
|
||||
<string name="settings_upgrade_status_label">Sponsurar CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Sponsurar CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Tes status da supporter.</string>
|
||||
<string name="upgrade_screen_why_title">Bials da l\'upgrade</string>
|
||||
<string name="upgrade_screen_how_title">Sco gidar</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Donează</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Am donat deja</string>
|
||||
<string name="foss_upgrade_no_money_label">Mi-am cheltuit toți banii pe AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS este gratuit si open source. Daca il gasiti util, luati in considerare sponsorizarea dezvoltarii pentru a ajuta la continuarea proiectului.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Sponsorizează dezvoltarea</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Fara reclame. Fara urmarire. Fara dependenta de Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Susținător din %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Mulțumim că sprijini dezvoltarea CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Deschide pagina de sponsorizare</string>
|
||||
<string name="settings_upgrade_status_label">Sponsorizează CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Sponsorizează CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Statusul tău de susținător.</string>
|
||||
<string name="upgrade_screen_why_title">Beneficiile upgrade-ului</string>
|
||||
<string name="upgrade_screen_how_title">Cum să ajuți</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Пожертвовать</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Я уже пожертвовал</string>
|
||||
<string name="foss_upgrade_no_money_label">Я потратил все свои деньги на AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS бесплатный и с открытым исходным кодом. Если Вам полезно приложение, рассмотрите возможность спонсировать разработку, чтобы помочь проекту продолжать развиваться.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Поддержать разработку</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Без рекламы. Без слежки. Без привязки к Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Спонсор с %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Спасибо за поддержку разработки CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Открыть страницу спонсорства</string>
|
||||
<string name="settings_upgrade_status_label">Поддержать CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Поддержать CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Статус Вашей поддержки.</string>
|
||||
<string name="upgrade_screen_why_title">Преимущества обновления</string>
|
||||
<string name="upgrade_screen_how_title">Как помочь</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Dona</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Apo giai donadu</string>
|
||||
<string name="foss_upgrade_no_money_label">Apo spèndidu totu is soddos meos pro is AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS est gratis e fonte aberta. Si t’est de agiudu, considera de patrotzinare su isviluppu pro agiudare a mantennere su progetu.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Sponsoriza s\'isvilupu</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Peroe publicidade. Peroe trachiamentu. Peroe dipendentzia de Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Sustentadore dae su %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Gràtzias pro sustènnere s\'isvilupu de CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Aberi sa pàgina de sponsorizatzione</string>
|
||||
<string name="settings_upgrade_status_label">Sostene CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Sostene CAPod</string>
|
||||
<string name="settings_upgrade_status_description">S\'istadu tuo de sustentadore.</string>
|
||||
<string name="upgrade_screen_why_title">Benefitzios de s\'agiornamentu</string>
|
||||
<string name="upgrade_screen_how_title">Comente agiudare</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">පරිත්යාග</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">මම දැනටමත් පරිත්යාග කර ඇත</string>
|
||||
<string name="foss_upgrade_no_money_label">මම මගේ මුදල් සියල්ල AirPods සඳහා වියදම් කරමි</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS නියමිත සහ විවෘත මූලාශ්රයි. ඔබට ඉත උපකාරී නම්, ආයෝජනය ගිණුම් සහය ගෙවිපීම කිරීමට අර්වමයක් ගෙවිපීම් ප්රස්තා කරන්නට සංවර්ධන කරන්න.</string>
|
||||
<string name="upgrade_foss_sponsor_action">සංවර්ධනයට අනුග්රහය දෙන්න</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">අළු නැත. ට්රැකිං නැත. Google Play සලකත්වය නැත.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">%s සිට අනුග්රාහකයෙකි</string>
|
||||
<string name="upgrade_foss_supporter_thanks">CAPod හි සංවර්ධනයට සහාය දැක්වූ ඔබට ස්තූතියි!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">අනුග්රාහක පිටුව විවෘත කරන්න</string>
|
||||
<string name="settings_upgrade_status_label">CAPod සඳහා අනුග්රහක්</string>
|
||||
<string name="upgrade_foss_sponsor_label">CAPod සඳහා අනුග්රහක්</string>
|
||||
<string name="settings_upgrade_status_description">ඔබේ සහයෝගිතා තත්වය.</string>
|
||||
<string name="upgrade_screen_why_title">උඩ්ග්රේඩ් ප්රතිලාභ</string>
|
||||
<string name="upgrade_screen_how_title">උදව් කරන්නේ කෙසේද</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Prispieť</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Už som prispel/a</string>
|
||||
<string name="foss_upgrade_no_money_label">Všetky peniaze som minul na AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS je zadarmo a open source. Ak ho nájdete užitočným, zvažte sponzorovanie vývoja, aby ste pomohli udržiavať projekt v chode.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Sponzorovaný vývoj</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Žiadne reklamy. Žiadne sledovanie. Žiadna závislosť od Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Podporovateľ od %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Ďakujeme, že podporujete vývoj CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Otvoriť stránku sponzorstva</string>
|
||||
<string name="settings_upgrade_status_label">Podporiť CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Podporiť CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Váš status podporovateľa.</string>
|
||||
<string name="upgrade_screen_why_title">Výhody upgradu</string>
|
||||
<string name="upgrade_screen_how_title">Ako pomôcť</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Doniraj</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Sem že doniral</string>
|
||||
<string name="foss_upgrade_no_money_label">Ves svoj denar porabim za AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS je brezplačen in odprtokoden. Če se vam zdi koristen, razmislite o sponzoriranju razvoja, da pomagate ohranjati projekt.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Podpri razvoj</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Brez oglasov. Brez sledenja. Brez vezave na Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Podpornik od %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Hvala, ker podpirate razvoj CAPod-a!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Odpri stran za sponzorstvo</string>
|
||||
<string name="settings_upgrade_status_label">Podpri CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Podpri CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Tvoj status podpornika.</string>
|
||||
<string name="upgrade_screen_why_title">Prednosti nadgradnje</string>
|
||||
<string name="upgrade_screen_how_title">Kako pomagati.</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Dhuro</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Unë tashmë kam dhuruar</string>
|
||||
<string name="foss_upgrade_no_money_label">Unë i shpenzoj të gjitha paratë e mia për AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS është falas dhe me burim të hapur. Nëse e gjeni të dobishmëm, konsideroni sponsorizimin e zhvillimit për ta mbajtur projektin gjallë.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Zhvillimi i sponsorëve</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Pa reklama. Pa gjurmim. Pa varësi nga Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Mbështetës që nga %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Faleminderit për mbështetjen e zhvillimit të CAPod-it!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Hap faqen e sponsorizimit</string>
|
||||
<string name="settings_upgrade_status_label">Mbështesni CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Mbështesni CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Statusi juaj si mbështetës.</string>
|
||||
<string name="upgrade_screen_why_title">Përfitime të përmirësimit</string>
|
||||
<string name="upgrade_screen_how_title">Si të ndihmoj</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Донације</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Већ сам донирао/ла</string>
|
||||
<string name="foss_upgrade_no_money_label">Сав новац трошим на АирПодс</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS је бесплатан и отвореног кода. Ако вам је користан, размотрите спонзорисање развоја да би пројекат остао жив.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Спонзориши развој</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Без реклама. Без праћења. Без зависности од Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Подржавалац од %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Хвала вам што подржавате развој CAPod-а!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Отворите страницу за спонзорство</string>
|
||||
<string name="settings_upgrade_status_label">Подржите CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Подржите CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Статус вашег подржавања.</string>
|
||||
<string name="upgrade_screen_why_title">Предности надградње</string>
|
||||
<string name="upgrade_screen_how_title">Како помоћи</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Donera</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Jag har redan donerat</string>
|
||||
<string name="foss_upgrade_no_money_label">Jag spenderar alla mina pengar på AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS är gratis och öppen källkod. Om du tycker att det är användbart kan du överväga att sponsra utvecklingen för att hjälpa till att hålla projektet vid liv.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Sponsra utvecklingen</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Inga annonser. Ingen spårning. Ingen bindning till Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Supporter sedan %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Tack för att du stödjer utvecklingen av CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Öppna sponsorsidan</string>
|
||||
<string name="settings_upgrade_status_label">Sponsra CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Sponsra CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Din supporterstatus.</string>
|
||||
<string name="upgrade_screen_why_title">Uppgraderingsfördelar</string>
|
||||
<string name="upgrade_screen_how_title">Hur du kan hjälpa</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Changia</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Nimeshachangia tayari</string>
|
||||
<string name="foss_upgrade_no_money_label">Natumia pesa zangu zote kwenye AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS ni bure na chanzo huria. Ikiwa unaikuta ni muhimu, fikiria kuchangia maendeleo ili kusaidia mradi kuendelea.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Faidhi maendeleo</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Hakuna matangazo. Hakuna ufuatiliaji. Hakuna kufungwa kwa Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Msaidizi tangu %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Asante kwa kuunga mkono maendeleo ya CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Fungua ukurasa wa udhamini</string>
|
||||
<string name="settings_upgrade_status_label">Kuunga mkono CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Kuunga mkono CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Hali yako ya kuunga mkono.</string>
|
||||
<string name="upgrade_screen_why_title">Faida za kuboreshwa</string>
|
||||
<string name="upgrade_screen_how_title">Jinsi ya kusaidia</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">நன்கொடை</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">நான் ஏற்கனவே நன்கொடை அளித்துவிட்டேன்</string>
|
||||
<string name="foss_upgrade_no_money_label">எனது பணத்தை எல்லாம் ஏர்பாட்களில் செலவிடுகிறேன்</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS இலவசமாகவும் திறழ் மூலக்கோடும் உள்ளது. இது பயனுள்ளதாக கணித்தால், தொடர்பு பொறுத்துக்க அம்சத்தை நிச்சயிக்கவும்.</string>
|
||||
<string name="upgrade_foss_sponsor_action">மேம்பாட்டை ஸ்பான்சர் செய்யுங்கள்</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">ஐவிளம்பரம் இல்லை. கண்காணிப்பு இல்லை. Google Play கட்டுப்பாட்டு இல்லை.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">%s முதல் ஆதரவாளர்</string>
|
||||
<string name="upgrade_foss_supporter_thanks">CAPod-இன் மேம்பாட்டை ஆதரித்ததற்கு நன்றி!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">ஸ்பான்சர் பக்கத்தைத் திற</string>
|
||||
<string name="settings_upgrade_status_label">CAPod ஐ ஆதரிக்கவும்</string>
|
||||
<string name="upgrade_foss_sponsor_label">CAPod ஐ ஆதரிக்கவும்</string>
|
||||
<string name="settings_upgrade_status_description">உங்கள் ஆதரவாளர் நிலை.</string>
|
||||
<string name="upgrade_screen_why_title">மேம்படுத்தல் நன்மைகள்</string>
|
||||
<string name="upgrade_screen_how_title">எப்படி உதவுவது</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">విరాళం ఇవ్వండి</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">నేను ఇప్పటికే విరాళం ఇచ్చాను</string>
|
||||
<string name="foss_upgrade_no_money_label">నా డబ్బు అంతా ఎయిర్పాడ్స్కే ఖర్చు చేస్తాను</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS ఉచిత మరియు ముక్త మూలం. ఇది ఉపయోగకరంగా అనిపిస్తే, ప్రాజెక్ట్ కొనసాగించడానికి అభివృద్ధిని స్పాన్సర్ చేయబక్తా పరిగణించండి.</string>
|
||||
<string name="upgrade_foss_sponsor_action">అభివృద్ధిని స్పాన్సర్ చేయండి</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">జాహీరాతులు లేవు. ట్రాకింగ్ లేదు. Google Play ఒక్కటికి పరిమితం కాదు.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">%s నుండి సపోర్టర్</string>
|
||||
<string name="upgrade_foss_supporter_thanks">CAPod అభివృద్ధికి మద్దతు ఇచ్చినందుకు ధన్యవాదాలు!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">స్పాన్సర్ పేజీని తెరవండి</string>
|
||||
<string name="settings_upgrade_status_label">CAPodకు స్పాన్సర్ చేయండి</string>
|
||||
<string name="upgrade_foss_sponsor_label">CAPodకు స్పాన్సర్ చేయండి</string>
|
||||
<string name="settings_upgrade_status_description">మీ సపోర్టర్ స్థితి.</string>
|
||||
<string name="upgrade_screen_why_title">అప్గ్రేడ్ ప్రయోజనాలు</string>
|
||||
<string name="upgrade_screen_how_title">ఎలా సహాయం చేయాలి</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">บริจาค</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">ฉันบริจาคแล้ว</string>
|
||||
<string name="foss_upgrade_no_money_label">ฉันใช้เงินทั้งหมดไปกับ AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS เป็นฟรีและโอเพนซอร์ส หากคุณเห็นว่ามีประโยชน์ ลองสนับสนุนการพัฒนาเพื่อช่วยให้โครงการดำเนินต่อไป</string>
|
||||
<string name="upgrade_foss_sponsor_action">สปอนเซอร์การพัฒนา</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">ไม่มีโฆษณา ไม่ติดตาม ไม่ผูกขาด Google Play</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">ผู้สนับสนุนตั้งแต่ %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">ขอบคุณที่สนับสนุนการพัฒนา CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">เปิดหน้าสนับสนุน</string>
|
||||
<string name="settings_upgrade_status_label">สนับสนุน CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">สนับสนุน CAPod</string>
|
||||
<string name="settings_upgrade_status_description">สถานะผู้สนับสนุนของคุณ</string>
|
||||
<string name="upgrade_screen_why_title">ประโยชน์ของการอัปเกรด</string>
|
||||
<string name="upgrade_screen_how_title">วิธีการช่วยเหลือ</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Bağış yap</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Zaten bağış yaptım</string>
|
||||
<string name="foss_upgrade_no_money_label">Tüm paramı AirPods\'a harcıyorum</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS ücretsiz ve açık kaynaklıdır. Eğer faydalı bulduysan, projenin devam edebilmesi için lütfen geliştirmeye destek olmayı düşün.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Geliştirmeyi destekleyin</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Reklam yok. Takip yok. Google Play sınırlaması yok.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">%s tarihinden beri destekçi</string>
|
||||
<string name="upgrade_foss_supporter_thanks">CAPod\'un geliştirilmesini desteklediğin için teşekkürler!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Sponsor sayfasını aç</string>
|
||||
<string name="settings_upgrade_status_label">CAPod\'u destekle</string>
|
||||
<string name="upgrade_foss_sponsor_label">CAPod\'u destekle</string>
|
||||
<string name="settings_upgrade_status_description">Destekçi durumun.</string>
|
||||
<string name="upgrade_screen_why_title">Yükseltme Avantajları</string>
|
||||
<string name="upgrade_screen_how_title">Nasıl yardım edilir</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Пожертва</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Вже пожертвував</string>
|
||||
<string name="foss_upgrade_no_money_label">Витратив усі гроші на AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS є безкоштовним та відкритим програмним забезпеченням. Якщо він вам корисний, розгляньте можливість спонсорування розробки, щоб допомогти проєкту продовжувати роботу.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Розвиток спонсорів</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Без реклами. Без стеження. Без прив\'язки до Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Підтримує з %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Дякуємо за підтримку розробки CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Відкрити сторінку спонсорства</string>
|
||||
<string name="settings_upgrade_status_label">Спонсорувати CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Спонсорувати CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Ваш статус прихильника.</string>
|
||||
<string name="upgrade_screen_why_title">Переваги оновлення</string>
|
||||
<string name="upgrade_screen_how_title">Як допомогти</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">عطیہ کریں۔</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">میں نے پہلے ہی عطیہ کر دیا ہے۔</string>
|
||||
<string name="foss_upgrade_no_money_label">میں اپنی تمام رقم ایئر پوڈز پر خرچ کرتا ہوں۔</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS مفت اور اوپن سورس ہے۔ اگر آپ اسے مفید پاتے ہیں، تو منصوبے کو سپانسر کرنے کا خیال کریں تاکہ پراجیکٹ جاری رہے۔</string>
|
||||
<string name="upgrade_foss_sponsor_action">ترقی کو اسپانسر کریں</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">کوئی اشتہار نہیں۔ کوئی ٹریکنگ نہیں۔ کوئی Google Play کی قید نہیں۔</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">%s سے سپورٹر</string>
|
||||
<string name="upgrade_foss_supporter_thanks">CAPod کی ترقی کی حمایت کرنے کا شکریہ!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">سپانسر پیج کھولیں</string>
|
||||
<string name="settings_upgrade_status_label">CAPod کی سرپرستی کریں</string>
|
||||
<string name="upgrade_foss_sponsor_label">CAPod کی سرپرستی کریں</string>
|
||||
<string name="settings_upgrade_status_description">آپ کی حامی حالت۔</string>
|
||||
<string name="upgrade_screen_why_title">اپ گریڈ کے فوائد</string>
|
||||
<string name="upgrade_screen_how_title">کیسے مدد کریں</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Xayriya qilish</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Men allaqachon xayriya qildim</string>
|
||||
<string name="foss_upgrade_no_money_label">Men butun pulimni AirPods uchun sarflayman</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS bepul va ochiq manbali. Agar foydali deb topsangiz, loyihani davom ettirishga yordam berish uchun rivojlantiruvchini qo\'llab-quvvatlashni ko\'rib chiqing.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Rivojlanishni sponsorlik qilish</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Reklamasiz. Kuzatishsiz. Google Play cheklovlarisiz.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">%s dan beri homiy</string>
|
||||
<string name="upgrade_foss_supporter_thanks">CAPod rivojlanishini qo\'llab-quvvatlaganingiz uchun rahmat!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Homiylik sahifasini ochish</string>
|
||||
<string name="settings_upgrade_status_label">CAPod ni homiylik qiling</string>
|
||||
<string name="upgrade_foss_sponsor_label">CAPod ni homiylik qiling</string>
|
||||
<string name="settings_upgrade_status_description">Sizning homiy holati.</string>
|
||||
<string name="upgrade_screen_why_title">Yangilashning afzalliklari</string>
|
||||
<string name="upgrade_screen_how_title">Qanday yordam berish</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Quyên tặng</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Tôi đã quyên góp</string>
|
||||
<string name="foss_upgrade_no_money_label">Tôi tiêu hết tiền cho AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS miễn phí và mã nguồn mở. Nếu bạn thấy nó hữu ích, hãy cân nhắc tài trợ cho việc phát triển để giúp duy trì dự án.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Tài trợ nhà phát triển</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Không quảng cáo. Không theo dõi. Không phụ thuộc Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Người ủng hộ từ %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Cảm ơn bạn đã ủng hộ sự phát triển của CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Mở trang tài trợ</string>
|
||||
<string name="settings_upgrade_status_label">Tài trợ cho CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Tài trợ cho CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Trạng thái nhà tài trợ của bạn.</string>
|
||||
<string name="upgrade_screen_why_title">Lợi ích nâng cấp</string>
|
||||
<string name="upgrade_screen_how_title">Làm thế nào để giúp đỡ</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">捐赠</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">已捐赠过了</string>
|
||||
<string name="foss_upgrade_no_money_label">钱都用来买 AirPods 了</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS 是免费开源的。如果您觉得它有用,请考虑赞助开发以帮助项目持续下去。</string>
|
||||
<string name="upgrade_foss_sponsor_action">赞助开发</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">无广告。无追踪。无 Google Play 绑定。</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">自 %s 起成为支持者</string>
|
||||
<string name="upgrade_foss_supporter_thanks">感谢您支持 CAPod 的开发!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">打开赞助页面</string>
|
||||
<string name="settings_upgrade_status_label">赞助 CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">赞助 CAPod</string>
|
||||
<string name="settings_upgrade_status_description">您的赞助者身份。</string>
|
||||
<string name="upgrade_screen_why_title">升级优势</string>
|
||||
<string name="upgrade_screen_how_title">如何帮助我们</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">資助</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">我已經資助了</string>
|
||||
<string name="foss_upgrade_no_money_label">我已經為 AirPods 傾家蕩產了</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS 是免費且開源的。如果您覺得它對您有幫助,請考慮贊助開發以支持項目繼續運行。</string>
|
||||
<string name="upgrade_foss_sponsor_action">贊助開發</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">沒有廣告。沒有追蹤。沒有 Google Play 鎖定。</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">自 %s 起成為贊助者</string>
|
||||
<string name="upgrade_foss_supporter_thanks">感謝你支持 CAPod 的開發!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">開啟贊助頁面</string>
|
||||
<string name="settings_upgrade_status_label">贊助 CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">贊助 CAPod</string>
|
||||
<string name="settings_upgrade_status_description">您的贊助者身份。</string>
|
||||
<string name="upgrade_screen_why_title">升級優勢</string>
|
||||
<string name="upgrade_screen_how_title">如何協助</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">抖內</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">我已經抖內了</string>
|
||||
<string name="foss_upgrade_no_money_label">我已經為 AirPods 傾家蕩產了</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS 是免費且開源的。如果您覺得它對您有幫助,請考慮贊助開發以支持專案繼續運行。</string>
|
||||
<string name="upgrade_foss_sponsor_action">贊助開發</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">無廣告。無追蹤。無 Google Play 鎖定。</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">自 %s 起成為贊助者</string>
|
||||
<string name="upgrade_foss_supporter_thanks">感謝您支持 CAPod 的開發!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">開啟贊助頁面</string>
|
||||
<string name="settings_upgrade_status_label">贊助 CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">贊助 CAPod</string>
|
||||
<string name="settings_upgrade_status_description">您的贊助狀態。</string>
|
||||
<string name="upgrade_screen_why_title">升級優勢</string>
|
||||
<string name="upgrade_screen_how_title">如何協助</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Nikela</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Senginikele kakade</string>
|
||||
<string name="foss_upgrade_no_money_label">Ngisebenzisa yonke imali yami kuma-AirPods</string>
|
||||
<string name="upgrade_foss_preamble">I-CAPod FOSS imahhala futhi ivulelwe umphakathi. Uma uyithola iyasiza, cabanga ngokuxhasa ukuthuthukiswa ukuze usizo kugcina iphrojekthi iqhubeka.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Xhasa ukuthuthukiswa</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Azikho izibhangeli. Akunakulandelelwa. Ayikho ukuvinjwa kwe-Google Play.</string>
|
||||
@@ -10,7 +7,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Umsekeli kusukela ngo-%s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Ngiyabonga ngokusekela ukuthuthukiswa kwe-CAPod!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Vula ikhasi lomxhasi</string>
|
||||
<string name="settings_upgrade_status_label">Uxhasa i-CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Uxhasa i-CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Isimo sakho sokusekela.</string>
|
||||
<string name="upgrade_screen_why_title">Imikhulu yokuphucula</string>
|
||||
<string name="upgrade_screen_how_title">Indlela yokusiza</string>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Donate</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">I already donated</string>
|
||||
<string name="foss_upgrade_no_money_label">I spend all my money on AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS is free and open source. If you find it useful, consider sponsoring development to help keep the project going.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Sponsor development</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">No ads. No tracking. No Google Play lock-in.</string>
|
||||
@@ -11,7 +8,7 @@
|
||||
<string name="upgrade_foss_supporter_since">Supporter since %s</string>
|
||||
<string name="upgrade_foss_supporter_thanks">Thank you for supporting CAPod\'s development!</string>
|
||||
<string name="upgrade_foss_sponsor_again_action">Open sponsor page</string>
|
||||
<string name="settings_upgrade_status_label">Sponsor CAPod</string>
|
||||
<string name="upgrade_foss_sponsor_label">Sponsor CAPod</string>
|
||||
<string name="settings_upgrade_status_description">Your supporter status.</string>
|
||||
<string name="upgrade_screen_why_title">Upgrade benefits</string>
|
||||
<string name="upgrade_screen_how_title">How to help</string>
|
||||
|
||||
@@ -2,7 +2,6 @@ package eu.darken.capod.common.upgrade.core
|
||||
|
||||
import android.app.Activity
|
||||
import com.android.billingclient.api.BillingClient.BillingResponseCode
|
||||
import com.android.billingclient.api.Purchase
|
||||
import eu.darken.capod.common.coroutine.AppScope
|
||||
import eu.darken.capod.common.datastore.value
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.ERROR
|
||||
@@ -18,11 +17,13 @@ import eu.darken.capod.common.upgrade.core.billing.BillingData
|
||||
import eu.darken.capod.common.upgrade.core.billing.BillingManager
|
||||
import eu.darken.capod.common.upgrade.core.billing.GplayServiceUnavailableException
|
||||
import eu.darken.capod.common.upgrade.core.billing.ItemAlreadyOwnedBillingException
|
||||
import eu.darken.capod.common.upgrade.core.billing.PendingPurchaseBillingException
|
||||
import eu.darken.capod.common.upgrade.core.billing.PurchasedSku
|
||||
import eu.darken.capod.common.upgrade.core.billing.Sku
|
||||
import eu.darken.capod.common.upgrade.core.billing.SkuDetails
|
||||
import eu.darken.capod.common.upgrade.core.billing.UserCanceledBillingException
|
||||
import eu.darken.capod.common.upgrade.core.billing.client.redacted
|
||||
import eu.darken.capod.common.upgrade.core.billing.work.PurchaseAckScheduler
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
@@ -58,6 +59,7 @@ class UpgradeRepoGplay @Inject constructor(
|
||||
private val billingManager: BillingManager,
|
||||
private val billingCache: BillingCache,
|
||||
private val curriculumVitae: CurriculumVitae,
|
||||
private val ackScheduler: PurchaseAckScheduler,
|
||||
) : UpgradeRepo {
|
||||
|
||||
override val storeSite: String = STORE_SITE
|
||||
@@ -137,6 +139,7 @@ class UpgradeRepoGplay @Inject constructor(
|
||||
.onEach { failedAt -> recordProUnconfirmed(failedAt) }
|
||||
.setupCommonEventHandlers(TAG) { "connectionFailureRecorder" }
|
||||
.launchIn(scope)
|
||||
|
||||
}
|
||||
|
||||
// Settledness travels WITH the ownership data (Info.isSettled), never on a parallel flow —
|
||||
@@ -255,6 +258,18 @@ class UpgradeRepoGplay @Inject constructor(
|
||||
return
|
||||
}
|
||||
try {
|
||||
// Persistent ack safety net, launch trigger: armed and AWAITED before the Play sheet
|
||||
// can open, so the WorkManager DB transaction lands even if the process dies around
|
||||
// the sheet — the exact window behind Play's unacknowledged-purchase auto-refunds.
|
||||
// Failure to arm never blocks the purchase; the foreground ack path still exists.
|
||||
try {
|
||||
ackScheduler.armForBillingFlowLaunch()
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
log(TAG, WARN) { "Failed to arm ack safety net for launch: ${e.asLog()}" }
|
||||
}
|
||||
|
||||
// Bounded, like every other Play path (refresh, restore, SKU query, ack). useConnection
|
||||
// waits for a healthy connection indefinitely, so a Play outage between rendering the
|
||||
// offers and this tap would park the launch forever — with launchBusySku still held,
|
||||
@@ -284,9 +299,18 @@ class UpgradeRepoGplay @Inject constructor(
|
||||
// Reconciled only if the restore actually returned the SKU Play claims is
|
||||
// owned — a grace-only isPro doesn't count, the entitlement is still missing.
|
||||
if (restored?.upgrades?.any { it.sku == sku } != true) {
|
||||
// Couldn't reconcile the entitlement (pending purchase, account mismatch,
|
||||
// Play quirk) — fall back to the already-owned dialog with restore tips.
|
||||
onError(e)
|
||||
if (restored?.pendingSkus?.isNotEmpty() == true) {
|
||||
// Play blocks re-purchasing a product whose payment it is still
|
||||
// processing. "Already owned" is technically what Play said, but the
|
||||
// dialog's restore tips are the wrong advice: nothing to restore, and
|
||||
// the entitlement lands by itself once the payment clears.
|
||||
log(TAG, INFO) { "Already-owned recovery found a pending payment" }
|
||||
onError(PendingPurchaseBillingException(e))
|
||||
} else {
|
||||
// Couldn't reconcile the entitlement (account mismatch, Play quirk) —
|
||||
// fall back to the already-owned dialog with restore tips.
|
||||
onError(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,12 +353,15 @@ class UpgradeRepoGplay @Inject constructor(
|
||||
|
||||
suspend fun querySkus(vararg skus: Sku): Collection<SkuDetails> = billingManager.querySkus(*skus)
|
||||
|
||||
// Strict subscription lookup for the pre-purchase gate: fresh SUBS-only query with explicit
|
||||
// failure. No grace substitution and no cross-product-type tolerance (unlike refresh() and
|
||||
// Strict purchase-state lookup for the pre-purchase gates: a fresh COMPLETE round-trip with
|
||||
// explicit failure. No grace substitution and no partial tolerance (unlike refresh() and
|
||||
// restorePurchaseNow()) — callers must treat any error as "couldn't verify" and fail closed.
|
||||
suspend fun queryCurrentSubscriptions(): Collection<Purchase> {
|
||||
log(TAG) { "queryCurrentSubscriptions()" }
|
||||
return billingManager.querySubscriptions()
|
||||
// Covers both product types and pending payments, because both can make a purchase wrong: a
|
||||
// renewing subscription (double billing) and a payment Play is still processing (Play rejects
|
||||
// the re-purchase).
|
||||
suspend fun verifyPurchaseStateNow(): Info {
|
||||
log(TAG) { "verifyPurchaseStateNow()" }
|
||||
return Info(billingData = billingManager.refreshStrict(), isSettled = true)
|
||||
}
|
||||
|
||||
override suspend fun refresh() {
|
||||
@@ -496,8 +523,8 @@ class UpgradeRepoGplay @Inject constructor(
|
||||
// Shared Pro/grace mapping used by both the reactive upgradeInfo flow and restorePurchaseNow().
|
||||
// Only relinquishes Pro if we haven't had it for a while (grace period). READ-ONLY: this runs on
|
||||
// replayed shared-flow data too, so it must never stamp the grace cache — see recordProState().
|
||||
// settled comes from the caller, never from billingData nullness: the grace branch returns an
|
||||
// Info with billingData = null that may well be settled (built from a real empty snapshot).
|
||||
// settled comes from the caller, never from billingData nullness: a null-data Info can be
|
||||
// perfectly settled (a real empty snapshot), and the grace branch carries data through anyway.
|
||||
private suspend fun BillingData?.toUpgradeInfo(settled: Boolean): Info {
|
||||
// Branch on MAPPED upgrades, not raw purchases: a purchase list containing only products
|
||||
// this app doesn't know maps to zero upgrades and must fall through to the grace check —
|
||||
@@ -513,7 +540,11 @@ class UpgradeRepoGplay @Inject constructor(
|
||||
return when {
|
||||
(now - lastProStateAt) < graceWindowMs() -> {
|
||||
log(TAG, VERBOSE) { "We are not pro, but were recently, did GPlay try annoy us again?" }
|
||||
Info(gracePeriod = true, billingData = null, isSettled = settled)
|
||||
// billingData is carried through, not dropped: this branch is only reached when the
|
||||
// mapped upgrades are empty, so entitlement stays empty either way — but a pending
|
||||
// payment must stay visible, and a grace user waiting on one is exactly who needs
|
||||
// the explanation.
|
||||
Info(gracePeriod = true, billingData = this, isSettled = settled)
|
||||
}
|
||||
|
||||
else -> mapped
|
||||
@@ -558,6 +589,34 @@ class UpgradeRepoGplay @Inject constructor(
|
||||
?.flatten()
|
||||
?: emptySet()
|
||||
|
||||
// Products with a payment Play is still processing. Deliberately NOT part of [isPro] or
|
||||
// [upgrades]: a pending payment grants nothing. It exists so the UI can explain the wait
|
||||
// and lock the purchase buttons — buying the alternative product now would double-charge.
|
||||
val pendingSkus: Collection<Sku> = billingData?.pendingPurchases
|
||||
?.map { purchase ->
|
||||
purchase.products.mapNotNull { productId ->
|
||||
val sku = OurSku.PRO_SKUS.singleOrNull { it.id == productId }
|
||||
if (sku == null) {
|
||||
log(TAG, WARN) { "Unknown pending product: $productId (${purchase.redacted()})" }
|
||||
return@mapNotNull null
|
||||
}
|
||||
sku
|
||||
}
|
||||
}
|
||||
?.flatten()
|
||||
?: emptySet()
|
||||
|
||||
// Any owned purchase Play still bills on a schedule. Deliberately computed from the RAW
|
||||
// PURCHASED purchases instead of [upgrades]: the mapping drops products this app doesn't
|
||||
// know, and the pre-purchase gate must keep blocking on a renewing subscription with an
|
||||
// unknown or legacy product ID — being wrong there means billing the user twice for Pro.
|
||||
// Both product types are scanned; a one-time purchase reports isAutoRenewing = false, so
|
||||
// the broader input cannot produce a false positive.
|
||||
// Computed on access, not in the initializer: an Info is built for every mapping pass, and
|
||||
// only the purchase gate needs this.
|
||||
val hasAutoRenewingSubscription: Boolean
|
||||
get() = billingData?.purchases?.any { it.isAutoRenewing } == true
|
||||
|
||||
override val isPro: Boolean = upgrades.isNotEmpty() || gracePeriod
|
||||
|
||||
override val upgradedAt: Instant? = upgrades
|
||||
|
||||
@@ -1,7 +1,28 @@
|
||||
package eu.darken.capod.common.upgrade.core.billing
|
||||
|
||||
import com.android.billingclient.api.Purchase
|
||||
import eu.darken.capod.common.upgrade.core.billing.client.isPurchased
|
||||
import eu.darken.capod.common.upgrade.core.billing.client.isRelevant
|
||||
|
||||
/**
|
||||
* Play's purchase state, split by what it may be used for. [purchases] is the entitlement carrier
|
||||
* and PURCHASED-only by construction, so no consumer can grant Pro (or stamp the grace cache) from
|
||||
* a payment Play is still processing; [pendingPurchases] keeps that payment visible to the UI.
|
||||
*/
|
||||
data class BillingData(
|
||||
val purchases: Collection<Purchase>
|
||||
)
|
||||
val purchases: Collection<Purchase>,
|
||||
val pendingPurchases: Collection<Purchase> = emptyList(),
|
||||
) {
|
||||
|
||||
companion object {
|
||||
// The one place raw Play data becomes a BillingData: splitting here (instead of at each
|
||||
// consumer) is what keeps "pending never grants Pro" a property of the type. Anything
|
||||
// that is neither PURCHASED nor PENDING is dropped — it is not an entitlement and not a
|
||||
// payment in progress.
|
||||
fun from(raw: Collection<Purchase>): BillingData {
|
||||
val relevant = raw.filter { it.isRelevant }
|
||||
val (purchased, pending) = relevant.partition { it.isPurchased }
|
||||
return BillingData(purchases = purchased, pendingPurchases = pending)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,9 @@ import eu.darken.capod.common.flow.setupCommonEventHandlers
|
||||
import eu.darken.capod.common.upgrade.core.billing.client.BillingClientException
|
||||
import eu.darken.capod.common.upgrade.core.billing.client.BillingConnection
|
||||
import eu.darken.capod.common.upgrade.core.billing.client.BillingConnectionProvider
|
||||
import eu.darken.capod.common.upgrade.core.billing.client.isPurchased
|
||||
import eu.darken.capod.common.upgrade.core.billing.client.redacted
|
||||
import eu.darken.capod.common.upgrade.core.billing.work.PurchaseAckScheduler
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
@@ -24,6 +26,8 @@ import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@@ -32,6 +36,7 @@ import javax.inject.Singleton
|
||||
class BillingManager @Inject constructor(
|
||||
@AppScope private val scope: CoroutineScope,
|
||||
connectionProvider: BillingConnectionProvider,
|
||||
private val ackScheduler: PurchaseAckScheduler,
|
||||
) {
|
||||
|
||||
// Fresh Play data plus its provenance: a query result covers owned products of the queried
|
||||
@@ -73,13 +78,13 @@ class BillingManager @Inject constructor(
|
||||
private val failedOnce = MutableStateFlow(false)
|
||||
val isFailureSettled: Flow<Boolean> = failedOnce
|
||||
|
||||
// Fires once per failed connect-loop iteration: connection setup failure, the mandatory initial
|
||||
// refreshPurchases erroring or timing out, an established connection dropping, an action-level
|
||||
// invalidation (SERVICE_DISCONNECTED/SERVICE_TIMEOUT from any useConnection call), or an
|
||||
// unexpected provider completion. Every one is a fresh reconciliation that couldn't confirm Pro.
|
||||
// The connect loop retries these internally and downstream flows just go quiet, so without this
|
||||
// explicit signal the grace episode clock (UpgradeRepoGplay.proUnconfirmedSince) would only
|
||||
// advance on an explicit ON_RESUME refresh().
|
||||
// Fires once per reconciliation that couldn't confirm Pro: a failed connect-loop iteration
|
||||
// (connection setup failure, the mandatory initial refreshPurchases erroring or timing out, an
|
||||
// established connection dropping, an action-level invalidation, an unexpected provider
|
||||
// completion) — and, via processReconciliation, a refresh that COMPLETED but only partially,
|
||||
// without a confirmed Pro purchase. The connect loop retries its failures internally and
|
||||
// downstream flows just go quiet, so without this explicit signal the grace episode clock
|
||||
// (UpgradeRepoGplay.proUnconfirmedSince) would only advance on an explicit ON_RESUME refresh().
|
||||
//
|
||||
// Each value is the failure's OCCURRENCE time (epoch millis). It has to be, not a bare Unit: the
|
||||
// channel buffers, and this feed and freshBillingData are separate flows with no cross-stream
|
||||
@@ -121,13 +126,18 @@ class BillingManager @Inject constructor(
|
||||
// isFailureSettled forever with no retry. withTimeoutOrNull, NOT
|
||||
// withTimeout: TimeoutCancellationException is a
|
||||
// CancellationException and would kill this loop.
|
||||
withTimeoutOrNull(INITIAL_REFRESH_TIMEOUT_MS) {
|
||||
val initialRefresh = withTimeoutOrNull(INITIAL_REFRESH_TIMEOUT_MS) {
|
||||
connection.refreshPurchases()
|
||||
} ?: throw BillingException("Initial purchase refresh timed out")
|
||||
|
||||
failStreak = 0
|
||||
connectionHolder.value = connection
|
||||
log(TAG, INFO) { "Billing connection established" }
|
||||
// AFTER publishing: a partial refresh is still a usable connection
|
||||
// (a pending-only cold start must not starve billingData), but its
|
||||
// bookkeeping — episode clock, dead-binder teardown — has to run,
|
||||
// and an invalidation may only tear down an INSTALLED connection.
|
||||
processReconciliation(initialRefresh)
|
||||
}
|
||||
// The provider flow stays open for the connection's lifetime; a normal
|
||||
// completion means the connection is gone without an error — treat it
|
||||
@@ -168,6 +178,11 @@ class BillingManager @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
// Serializes acknowledgement work between the reactive ack collector and explicit
|
||||
// ensureAllAcknowledged() sweeps (PurchaseAckWorker): both paths mutate the token bookkeeping
|
||||
// sets and both must never double-drive the same purchase's inline retry sequence.
|
||||
private val ackMutex = Mutex()
|
||||
|
||||
// Re-drives the ack pass WITHOUT a new purchases emission: `purchases` is distinctUntilChanged,
|
||||
// so a refresh returning a byte-identical (still unacknowledged) list is deduped and could never
|
||||
// retry a failed ack -- the pipeline starved until Play sent something different. Declared ahead
|
||||
@@ -214,7 +229,7 @@ class BillingManager @Inject constructor(
|
||||
.shareIn(scope, WhileSubscribed(3000L, 0L), replay = 1)
|
||||
|
||||
val billingData: Flow<BillingData> = purchases
|
||||
.map { BillingData(purchases = it) }
|
||||
.map { BillingData.from(it) }
|
||||
.shareIn(scope, WhileSubscribed(3000L, 0L), replay = 1)
|
||||
|
||||
val purchaseFailures: Flow<BillingResult> = connectionHolder
|
||||
@@ -235,7 +250,10 @@ class BillingManager @Inject constructor(
|
||||
// every emission here is a real Play round-trip the grace bookkeeping needs.
|
||||
.resubscribeOnFailure("freshBillingData")
|
||||
}
|
||||
.map { FreshData(data = BillingData(purchases = it.purchases), isFullSnapshot = it.isFullSnapshot, occurredAt = it.occurredAt) }
|
||||
// Through from() like every other exit, although the connection only ever puts PURCHASED
|
||||
// purchases on this stream: if that invariant ever broke, splitting keeps the grace
|
||||
// bookkeeping from stamping a pending payment as a confirmation.
|
||||
.map { FreshData(data = BillingData.from(it.purchases), isFullSnapshot = it.isFullSnapshot, occurredAt = it.occurredAt) }
|
||||
.setupCommonEventHandlers(TAG) { "freshBillingData" }
|
||||
// Same belt as `purchases`: an Eagerly shared flow that dies stays dead, and this one feeds
|
||||
// both the grace bookkeeping and the ack collector's re-drive signal.
|
||||
@@ -247,13 +265,13 @@ class BillingManager @Inject constructor(
|
||||
// below. The immutable Purchase snapshot keeps reporting isAcknowledged=false until a fresh Play
|
||||
// query supersedes it, so the ack re-fires every emission until then; re-acking is a documented
|
||||
// no-op on Play's side, whereas skipping a needed ack gets the purchase auto-refunded after 3
|
||||
// days -- so the ack stays unconditional and this set only quiets the log spam. Single
|
||||
// sequential collector (the ack pass below), no locking needed.
|
||||
// days -- so the ack stays unconditional and this set only quiets the log spam. Confined by
|
||||
// ackMutex (the collector's pass and explicit sweeps both run under it).
|
||||
private val loggedAckTokens = mutableSetOf<String>()
|
||||
|
||||
// Tokens whose PERMANENT ack failure was already reported. Play will keep rejecting these
|
||||
// (developer error, item not owned, unsupported feature), so the bug report fires once per token
|
||||
// instead of once per pass. Same single-collector confinement as loggedAckTokens.
|
||||
// instead of once per pass. Same ackMutex confinement as loggedAckTokens.
|
||||
private val reportedAckFailures = mutableSetOf<String>()
|
||||
|
||||
// At most one reschedule timer in flight: repeated failures must not stack timers.
|
||||
@@ -287,11 +305,22 @@ class BillingManager @Inject constructor(
|
||||
// .isAcknowledged, whose immutable snapshot stays false until a fresh Play query.
|
||||
private enum class AckOutcome { SUCCESS, TRANSIENT, PERMANENT }
|
||||
|
||||
// Aggregate outcome of one ack pass; ensureAllAcknowledged() maps it to a sweep result.
|
||||
data class AckPassOutcome(val transient: Int, val permanent: Int)
|
||||
|
||||
// One acknowledgement pass over the canonical purchase list. Never throws except cancellation:
|
||||
// transient failures schedule a re-drive, permanent ones are reported and left to organic fresh
|
||||
// -data signals.
|
||||
private suspend fun runAckPass(purchases: Collection<Purchase>) {
|
||||
private suspend fun runAckPass(purchases: Collection<Purchase>): AckPassOutcome = ackMutex.withLock {
|
||||
val needAck = purchases.filter {
|
||||
// The canonical list carries pending payments too. Play rejects acknowledging one
|
||||
// PERMANENTLY, so an unfiltered pass would fire a bug report for every pending purchase,
|
||||
// every pass — and there is nothing to acknowledge until the payment completes anyway.
|
||||
if (!it.isPurchased) {
|
||||
log(TAG) { "Not acknowledgeable yet: ${it.redacted()}" }
|
||||
return@filter false
|
||||
}
|
||||
|
||||
val needsAck = !it.isAcknowledged
|
||||
|
||||
if (needsAck) log(TAG) { "Needs ACK: ${it.redacted()}" }
|
||||
@@ -300,7 +329,23 @@ class BillingManager @Inject constructor(
|
||||
needsAck
|
||||
}
|
||||
|
||||
if (needAck.isNotEmpty()) {
|
||||
// Arm the persistent safety net BEFORE attempting anything, and AWAIT the enqueue (the
|
||||
// scheduler bounds it): the inline retries below can span minutes, and a process death
|
||||
// inside them must not strand the purchase until Play's 3-day auto-refund. A deferred
|
||||
// signal (channel + collector) would reintroduce exactly that window. Fail-open: the
|
||||
// net is an extra layer, never a reason to skip the acks themselves.
|
||||
try {
|
||||
ackScheduler.armForUnackedPurchases(needAck.maxOf { it.purchaseTime } + ACK_SAFETY_NET_DEADLINE_MS)
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
log(TAG, WARN) { "Failed to arm ack safety net: ${e.asLog()}" }
|
||||
}
|
||||
}
|
||||
|
||||
var transientFailures = 0
|
||||
var permanentFailures = 0
|
||||
|
||||
for (purchase in needAck) {
|
||||
// First ack of a token is INFO; idempotent repeats drop to DEBUG. This never gates the
|
||||
@@ -364,6 +409,7 @@ class BillingManager @Inject constructor(
|
||||
}
|
||||
|
||||
if (outcome == AckOutcome.TRANSIENT) transientFailures++
|
||||
if (outcome == AckOutcome.PERMANENT) permanentFailures++
|
||||
if (abortPass) break
|
||||
}
|
||||
|
||||
@@ -374,6 +420,41 @@ class BillingManager @Inject constructor(
|
||||
}
|
||||
scheduleAckRetry()
|
||||
}
|
||||
|
||||
AckPassOutcome(transient = transientFailures, permanent = permanentFailures)
|
||||
}
|
||||
|
||||
// Outcome of an explicit safety-net sweep, see ensureAllAcknowledged().
|
||||
enum class AckSweepResult { COMPLETE, RETRY, PERMANENT_FAILURE }
|
||||
|
||||
/**
|
||||
* One self-contained acknowledgement sweep for the persistent safety net (PurchaseAckWorker):
|
||||
* refresh from Play, then acknowledge everything unacknowledged IN THIS COROUTINE. The reactive
|
||||
* ack collector consumes purchase state asynchronously, so a caller that needs proof the acks
|
||||
* actually happened before it reports success (a worker deciding success vs retry) cannot rely
|
||||
* on it. Never throws except cancellation.
|
||||
*/
|
||||
suspend fun ensureAllAcknowledged(): AckSweepResult {
|
||||
log(TAG) { "ensureAllAcknowledged()" }
|
||||
val fresh = try {
|
||||
useConnection { refreshPurchases() }
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
log(TAG, WARN) { "ensureAllAcknowledged(): refresh failed: ${e.asLog()}" }
|
||||
return AckSweepResult.RETRY
|
||||
}
|
||||
// Same bookkeeping every other refresh exit owes: grace episode clock + dead-binder teardown.
|
||||
processReconciliation(fresh)
|
||||
val outcome = runAckPass(fresh.purchases)
|
||||
return when {
|
||||
// An incomplete refresh may be hiding an unacknowledged purchase of the failed type,
|
||||
// and a transient ack failure is retriable by definition.
|
||||
outcome.transient > 0 || !fresh.isComplete -> AckSweepResult.RETRY
|
||||
// Play will keep rejecting these no matter how often the worker comes back.
|
||||
outcome.permanent > 0 -> AckSweepResult.PERMANENT_FAILURE
|
||||
else -> AckSweepResult.COMPLETE
|
||||
}
|
||||
}
|
||||
|
||||
// A purchase Play will keep rejecting: report it once per token, then stay quiet. The pass still
|
||||
@@ -401,6 +482,39 @@ class BillingManager @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
// A partial refresh no longer reaches useConnection's dead-binder detection (it returns instead
|
||||
// of throwing), so the teardown that used to ride the throw path happens here. Cause chain, not
|
||||
// the exception itself: the failure arrives user-friendly-mapped. Deliberately no holder CAS:
|
||||
// the failing connection may already have been replaced, and the accepted cost of that rare
|
||||
// race is one extra failed action while the loop reconnects.
|
||||
private fun invalidateOnDeadConnection(refresh: BillingConnection.PurchaseRefresh) {
|
||||
val clientError = refresh.partialError?.let {
|
||||
(it as? BillingClientException) ?: (it.cause as? BillingClientException)
|
||||
}
|
||||
if (clientError != null && clientError.result.responseCode in INVALIDATING_CODES) {
|
||||
log(TAG, WARN) { "Refresh reported the connection dead (${clientError.result.responseCode}), invalidating." }
|
||||
invalidations.trySend(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
// Everything a COMPLETED refresh owes the rest of the app, in one place: both the connect loop's
|
||||
// initial refresh and manual refresh() calls run through it, so a Restore tap during an outage
|
||||
// feeds the same bookkeeping the connect loop does. Only reached when refreshPurchases returned
|
||||
// (it still throws when it found nothing AND a query failed — that path is the connect loop's /
|
||||
// useConnection's).
|
||||
private fun processReconciliation(refresh: BillingConnection.PurchaseRefresh) {
|
||||
invalidateOnDeadConnection(refresh)
|
||||
|
||||
if (!refresh.isComplete && !refresh.hasConfirmedProPurchase) {
|
||||
// A reconciliation that couldn't confirm Pro. Stamped with the refresh's COMMIT time,
|
||||
// never now-at-send: a confirmation that committed between this refresh and the send
|
||||
// (e.g. a pending payment completing) must stay NEWER than this failure, or the grace
|
||||
// episode it closed would be reopened.
|
||||
log(TAG, WARN) { "Partial refresh without a confirmed Pro purchase at ${refresh.occurredAt}" }
|
||||
connectionFailuresChannel.trySend(refresh.occurredAt)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun <T> useConnection(action: suspend BillingConnection.() -> T): T {
|
||||
// Every caller here is active demand (opening the upgrade screen, restore/buy taps,
|
||||
// purchase acks) — cut a pending reconnect backoff short. A no-op while healthy.
|
||||
@@ -479,18 +593,29 @@ class BillingManager @Inject constructor(
|
||||
// shared upgradeInfo replay cache. The freshBillingData emission happens inside the
|
||||
// reducer's commit, in commit order — not here.
|
||||
val fresh = useConnection { refreshPurchases() }
|
||||
return BillingData(purchases = fresh.purchases)
|
||||
processReconciliation(fresh)
|
||||
return BillingData.from(fresh.purchases)
|
||||
}
|
||||
|
||||
// Strict SUBS-only query for the pre-purchase subscription gate: unlike refresh(), a failure
|
||||
// here propagates (user-friendly-mapped) instead of being masked by the other product type.
|
||||
suspend fun querySubscriptions(): Collection<Purchase> = try {
|
||||
useConnection { querySubscriptions() }
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
log(TAG, WARN) { "querySubscriptions() failed: ${e.asLog()}" }
|
||||
throw e.tryMapUserFriendly()
|
||||
// Strict variant for the pre-purchase gates: unlike refresh(), anything short of a COMPLETE
|
||||
// reconciliation throws (user-friendly-mapped) instead of returning what it happened to find —
|
||||
// a gate must be able to tell "not owned" apart from "couldn't verify" and fail closed.
|
||||
suspend fun refreshStrict(): BillingData {
|
||||
log(TAG) { "refreshStrict()" }
|
||||
val fresh = useConnection { refreshPurchases() }
|
||||
// A gate that hit a dying connection must still trigger the reconnect (the throw below
|
||||
// bypasses useConnection's detection, which already returned), or every later purchase
|
||||
// check keeps reusing the corpse. No-op on a complete refresh. The episode clock stays out
|
||||
// of this path: an aborted gate is not a reconciliation outcome.
|
||||
invalidateOnDeadConnection(fresh)
|
||||
if (!fresh.isComplete) {
|
||||
// partialError is set for every incomplete refresh; the fallback only exists so a
|
||||
// future incompleteness without a captured cause still fails closed instead of passing.
|
||||
val error = fresh.partialError ?: BillingException("Purchase refresh was incomplete")
|
||||
log(TAG, WARN) { "refreshStrict() incomplete: ${error.asLog()}" }
|
||||
throw error.tryMapUserFriendly()
|
||||
}
|
||||
return BillingData.from(fresh.purchases)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -525,6 +650,10 @@ class BillingManager @Inject constructor(
|
||||
BillingResponseCode.ITEM_NOT_OWNED,
|
||||
)
|
||||
|
||||
// Play auto-refunds purchases not acknowledged within 3 days; every safety-net deadline
|
||||
// derives from this.
|
||||
const val ACK_SAFETY_NET_DEADLINE_MS = 3 * 24 * 60 * 60 * 1000L
|
||||
|
||||
private const val INITIAL_REFRESH_TIMEOUT_MS = 30_000L
|
||||
private const val MAX_BACKOFF_MS = 300_000L
|
||||
|
||||
|
||||
+3
-1
@@ -10,6 +10,7 @@ import eu.darken.capod.common.debug.logging.Logging.Priority.ERROR
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.error.HasLocalizedError
|
||||
import eu.darken.capod.common.error.LocalizedError
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
|
||||
class GplayServiceUnavailableException(cause: Throwable) :
|
||||
BillingException("Google Play services are unavailable.", cause), HasLocalizedError {
|
||||
@@ -48,10 +49,11 @@ class GplayServiceUnavailableException(cause: Throwable) :
|
||||
)
|
||||
|
||||
private fun onLaunchFailed(e: Exception) {
|
||||
log(ERROR) { "Can't launch settings intent for Google Play: $e" }
|
||||
log(TAG, ERROR) { "Can't launch settings intent for Google Play: $e" }
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val GPLAY_PKG = "com.android.vending"
|
||||
private val TAG = logTag("Upgrade", "Gplay", "ServiceUnavailable")
|
||||
}
|
||||
}
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package eu.darken.capod.common.upgrade.core.billing
|
||||
|
||||
/**
|
||||
* A purchase can't proceed because the account already has a payment Play is still processing.
|
||||
*
|
||||
* Typed so the UI can answer with the informational pending dialog instead of the already-owned
|
||||
* error and its restore tips: restoring cannot help — Play refuses to re-sell a product with a
|
||||
* pending payment, and the entitlement arrives on its own once the payment clears.
|
||||
*/
|
||||
class PendingPurchaseBillingException(cause: Throwable? = null) :
|
||||
BillingException("A purchase with a pending payment already exists.", cause)
|
||||
+15
@@ -8,6 +8,21 @@ import com.android.billingclient.api.Purchase
|
||||
internal val BillingResult.isSuccess: Boolean
|
||||
get() = responseCode == BillingClient.BillingResponseCode.OK
|
||||
|
||||
/**
|
||||
* Owned right now. The ONLY state that may grant an entitlement, stamp the Pro grace cache or be
|
||||
* acknowledged — a PENDING purchase is a payment Play is still processing, and acknowledging one is
|
||||
* rejected permanently.
|
||||
*/
|
||||
internal val Purchase.isPurchased: Boolean
|
||||
get() = purchaseState == Purchase.PurchaseState.PURCHASED
|
||||
|
||||
/**
|
||||
* Worth carrying in our state at all: owned, or a payment in progress the user should see. Anything
|
||||
* else (UNSPECIFIED_STATE) is dropped at ingestion — it is neither.
|
||||
*/
|
||||
internal val Purchase.isRelevant: Boolean
|
||||
get() = isPurchased || purchaseState == Purchase.PurchaseState.PENDING
|
||||
|
||||
/**
|
||||
* Log-safe rendering of a [Purchase].
|
||||
*
|
||||
|
||||
+82
-70
@@ -7,7 +7,6 @@ import com.android.billingclient.api.BillingClient.BillingResponseCode
|
||||
import com.android.billingclient.api.BillingFlowParams
|
||||
import com.android.billingclient.api.BillingResult
|
||||
import com.android.billingclient.api.Purchase
|
||||
import com.android.billingclient.api.Purchase.PurchaseState
|
||||
import com.android.billingclient.api.QueryProductDetailsParams
|
||||
import com.android.billingclient.api.QueryProductDetailsResult
|
||||
import com.android.billingclient.api.QueryPurchasesParams
|
||||
@@ -41,11 +40,11 @@ class BillingConnection(
|
||||
private val skuTypeOf: (String) -> Sku.Type? = DEFAULT_SKU_TYPE_RESOLVER,
|
||||
) {
|
||||
|
||||
// A purchase proven by an onPurchasesUpdated success event. Additive only: events prove
|
||||
// ownership, never absence. `gen` orders it against queries (a query that STARTED before this
|
||||
// event must not clear it); `type` is resolved at ingestion so a later per-type query that
|
||||
// confirms absence can supersede it (null = product unknown to this app, only a complete
|
||||
// refresh may clear it).
|
||||
// A purchase (owned or with a pending payment) proven by an onPurchasesUpdated success event.
|
||||
// Additive only: events prove existence, never absence. `gen` orders it against queries (a
|
||||
// query that STARTED before this event must not clear it); `type` is resolved at ingestion so a
|
||||
// later per-type query that confirms absence can supersede it (null = product unknown to this
|
||||
// app, only a complete refresh may clear it).
|
||||
data class OverlayEntry(
|
||||
val purchase: Purchase,
|
||||
val gen: Long,
|
||||
@@ -64,11 +63,11 @@ class BillingConnection(
|
||||
) {
|
||||
|
||||
internal fun withEvent(
|
||||
purchased: Collection<Purchase>,
|
||||
relevant: Collection<Purchase>,
|
||||
typeOf: (String) -> Sku.Type?,
|
||||
): ReducerState {
|
||||
val gen = eventGen + 1
|
||||
val entries = purchased.map { purchase ->
|
||||
val entries = relevant.map { purchase ->
|
||||
OverlayEntry(
|
||||
purchase = purchase,
|
||||
gen = gen,
|
||||
@@ -169,10 +168,13 @@ class BillingConnection(
|
||||
"onPurchasesUpdated(code=${result.responseCode}, message=${result.debugMessage}, " +
|
||||
"purchases=${purchases?.redacted()})"
|
||||
}
|
||||
// PENDING purchases must never surface as owned (or stamp the Pro grace cache).
|
||||
val purchased = purchases.orEmpty().filter { it.purchaseState == PurchaseState.PURCHASED }
|
||||
// The reducer carries PENDING purchases too (the UI must be able to show a payment in
|
||||
// progress), but the fresh stream stays PURCHASED-only: it feeds the entitlement and
|
||||
// grace bookkeeping, which must never see a payment Play hasn't completed.
|
||||
val relevant = purchases.orEmpty().filter { it.isRelevant }
|
||||
val purchased = relevant.filter { it.isPurchased }
|
||||
synchronized(reducerLock) {
|
||||
state.value = state.value.withEvent(purchased, skuTypeOf)
|
||||
state.value = state.value.withEvent(relevant, skuTypeOf)
|
||||
if (purchased.isNotEmpty()) {
|
||||
freshUpdatesChannel.trySend(FreshUpdate(purchased, isFullSnapshot = false))
|
||||
}
|
||||
@@ -193,12 +195,29 @@ class BillingConnection(
|
||||
failureChannel.close()
|
||||
}
|
||||
|
||||
// The purchases of a refresh plus whether it covered both product types: a partial result (one
|
||||
// query failed) is still authoritative for what it FOUND, but must not be treated as proof of
|
||||
// absence for the type that couldn't be checked.
|
||||
// The full outcome of a refresh: what it committed, what it actually CONFIRMED, and how far it
|
||||
// got. A partial result (one query failed) is still authoritative for what it found, but must
|
||||
// not be treated as proof of absence for the type that couldn't be checked — so callers that
|
||||
// need to fail closed, or to feed the grace episode clock, get the provenance instead of having
|
||||
// to infer it from the merged view.
|
||||
data class PurchaseRefresh(
|
||||
// The committed reducer state (queries merged with retained snapshots and surviving
|
||||
// events) — the same view the reactive purchases flow emits.
|
||||
val purchases: Collection<Purchase>,
|
||||
// ONLY what these queries returned: never retained state of a failed type, so a consumer
|
||||
// can tell "Play said so just now" from "we still remember this".
|
||||
val confirmed: Collection<Purchase> = emptyList(),
|
||||
// A confirmed PURCHASED purchase of a product this app knows. Fail-safe default: "we
|
||||
// couldn't confirm Pro" is the direction that keeps the grace bookkeeping honest.
|
||||
val hasConfirmedProPurchase: Boolean = false,
|
||||
val isComplete: Boolean,
|
||||
// Commit time under reducerLock — the same instant stamped on this refresh's FreshUpdate,
|
||||
// so a confirmation and a later failure signal are ordered by when they HAPPENED.
|
||||
val occurredAt: Long = System.currentTimeMillis(),
|
||||
// Why the refresh is incomplete (the failed type's already user-friendly-mapped
|
||||
// exception), null when complete. Carried rather than thrown: a partial refresh that found
|
||||
// something is still useful, only the caller can decide whether partial is good enough.
|
||||
val partialError: Throwable? = null,
|
||||
)
|
||||
|
||||
// Serializes concurrent refreshes (manual, background, auto-restore): an older query that got
|
||||
@@ -214,8 +233,8 @@ class BillingConnection(
|
||||
coroutineScope {
|
||||
log(TAG) { "refreshPurchases()" }
|
||||
val genAtQueryStart = state.value.eventGen
|
||||
val iapJob = async { queryPurchasedProducts(BillingClient.ProductType.INAPP) }
|
||||
val subJob = async { queryPurchasedProducts(BillingClient.ProductType.SUBS) }
|
||||
val iapJob = async { queryRelevantProducts(BillingClient.ProductType.INAPP) }
|
||||
val subJob = async { queryRelevantProducts(BillingClient.ProductType.SUBS) }
|
||||
val iap = iapJob.await()
|
||||
val sub = subJob.await()
|
||||
log(TAG) { "Refreshed IAPs=${iap.getOrNull()?.redacted()}, SUBs=${sub.getOrNull()?.redacted()}" }
|
||||
@@ -224,6 +243,12 @@ class BillingConnection(
|
||||
// authoritative even when its sibling failed — verified absence (e.g. a refunded IAP)
|
||||
// must not be discarded just because the SUB query errored.
|
||||
val isComplete = iap.isSuccess && sub.isSuccess
|
||||
// Only what the queries CONFIRMED as owned — retained stale data of a failed type, and
|
||||
// pending payments, stay out (both would keep re-stamping the grace window).
|
||||
val confirmed = (iap.getOrNull().orEmpty() + sub.getOrNull().orEmpty())
|
||||
.filter { it.isPurchased }
|
||||
.sortedByDescending { it.purchaseTime }
|
||||
var committedAt = 0L
|
||||
val committed = synchronized(reducerLock) {
|
||||
val next = state.value.withQueryResults(
|
||||
iap = iap.getOrNull(),
|
||||
@@ -231,17 +256,18 @@ class BillingConnection(
|
||||
genAtQueryStart = genAtQueryStart,
|
||||
)
|
||||
state.value = next
|
||||
committedAt = System.currentTimeMillis()
|
||||
if (iap.isSuccess || sub.isSuccess) {
|
||||
// Only what the queries CONFIRMED — retained stale data of a failed type stays
|
||||
// out of the fresh stream (it would keep re-stamping the grace window).
|
||||
val confirmed = (iap.getOrNull().orEmpty() + sub.getOrNull().orEmpty())
|
||||
.sortedByDescending { it.purchaseTime }
|
||||
// A surviving overlay entry (purchase event newer than the query start, or of
|
||||
// a failed type) means this result does NOT prove total absence: it must not
|
||||
// count as a full snapshot, or an empty query racing a fresh purchase event
|
||||
// would start a false unconfirmed-grace episode.
|
||||
val provesAbsence = isComplete && next.overlay.isEmpty()
|
||||
freshUpdatesChannel.trySend(FreshUpdate(confirmed, isFullSnapshot = provesAbsence))
|
||||
// A surviving OWNED overlay entry (purchase event newer than the query start,
|
||||
// or of a failed type) means this result does NOT prove total absence: it must
|
||||
// not count as a full snapshot, or an empty query racing a fresh purchase event
|
||||
// would start a false unconfirmed-grace episode. A surviving PENDING entry
|
||||
// proves nothing about ownership, so it must not suppress the bookkeeping
|
||||
// either — a payment in progress would otherwise freeze the episode clock.
|
||||
val provesAbsence = isComplete && next.overlay.none { it.purchase.isPurchased }
|
||||
freshUpdatesChannel.trySend(
|
||||
FreshUpdate(confirmed, isFullSnapshot = provesAbsence, occurredAt = committedAt)
|
||||
)
|
||||
}
|
||||
next
|
||||
}
|
||||
@@ -249,31 +275,42 @@ class BillingConnection(
|
||||
// Support-log anchor, at INFO because purchase complaints arrive as debug recordings.
|
||||
// Logs what these queries CONFIRMED, kept distinct from the committed view: merged()
|
||||
// retains a failed type's previous purchases, so reporting it as "what Play returned"
|
||||
// would be the same false-certainty trap the copy elsewhere had to fix. Product IDs
|
||||
// only -- never the Purchase, which carries order and token data.
|
||||
// would be the same false-certainty trap the copy elsewhere had to fix. Pending ids are
|
||||
// listed separately — "bought it, still not Pro" reports are exactly this state.
|
||||
// Product IDs only -- never the Purchase, which carries order and token data.
|
||||
log(TAG, INFO) {
|
||||
val confirmedIds = (iap.getOrNull().orEmpty() + sub.getOrNull().orEmpty()).flatMap { it.products }
|
||||
"refreshPurchases(): confirmed=$confirmedIds, isComplete=$isComplete, " +
|
||||
val returned = iap.getOrNull().orEmpty() + sub.getOrNull().orEmpty()
|
||||
val confirmedIds = returned.filter { it.isPurchased }.flatMap { it.products }
|
||||
val pendingIds = returned.filterNot { it.isPurchased }.flatMap { it.products }
|
||||
"refreshPurchases(): confirmed=$confirmedIds, pending=$pendingIds, isComplete=$isComplete, " +
|
||||
"iapOk=${iap.isSuccess}, subOk=${sub.isSuccess}, merged=${committed.merged().size}"
|
||||
}
|
||||
|
||||
// Throws when nothing was found and a query failed, so the caller can tell "not
|
||||
// owned" apart from "couldn't verify".
|
||||
combinePurchaseResults(iap, sub)
|
||||
combinePurchaseResults(iap, sub, skuTypeOf)
|
||||
|
||||
PurchaseRefresh(
|
||||
purchases = committed.merged(),
|
||||
confirmed = confirmed,
|
||||
hasConfirmedProPurchase = confirmed.any { purchase ->
|
||||
purchase.products.any { skuTypeOf(it) != null }
|
||||
},
|
||||
isComplete = isComplete,
|
||||
occurredAt = committedAt,
|
||||
partialError = iap.exceptionOrNull() ?: sub.exceptionOrNull(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Never throws except on cancellation, so a single failing product-type query doesn't cancel
|
||||
// the sibling query (or the coroutineScope). The exception is already user-friendly-mapped.
|
||||
private suspend fun queryPurchasedProducts(
|
||||
// Returns owned AND pending purchases; the split by state happens where it matters (fresh
|
||||
// stream, entitlement mapping), so a pending payment stays visible instead of vanishing here.
|
||||
private suspend fun queryRelevantProducts(
|
||||
@BillingClient.ProductType type: String,
|
||||
): Result<Collection<Purchase>> = try {
|
||||
Result.success(queryPurchases(type).filter { it.purchaseState == PurchaseState.PURCHASED })
|
||||
Result.success(queryPurchases(type).filter { it.isRelevant })
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
@@ -307,37 +344,6 @@ class BillingConnection(
|
||||
return purchaseData
|
||||
}
|
||||
|
||||
// Strict SUBS-only query for the pre-purchase subscription gate: unlike refreshPurchases(),
|
||||
// a failure propagates (no cross-type tolerance) — callers must be able to fail closed on
|
||||
// "couldn't verify". Commits through the reducer like any query, so the reactive purchases
|
||||
// flow picks up the fresh renewal state, and emits a partial fresh update: it proves what the
|
||||
// SUBS query found, never the absence of anything it didn't cover.
|
||||
suspend fun querySubscriptions(): Collection<Purchase> = refreshMutex.withLock {
|
||||
log(TAG) { "querySubscriptions()" }
|
||||
val genAtQueryStart = state.value.eventGen
|
||||
val subs = queryPurchases(BillingClient.ProductType.SUBS)
|
||||
.filter { it.purchaseState == PurchaseState.PURCHASED }
|
||||
val committed = synchronized(reducerLock) {
|
||||
val next = state.value.withQueryResults(
|
||||
iap = null,
|
||||
sub = subs,
|
||||
genAtQueryStart = genAtQueryStart,
|
||||
)
|
||||
state.value = next
|
||||
freshUpdatesChannel.trySend(FreshUpdate(subs, isFullSnapshot = false))
|
||||
next
|
||||
}
|
||||
// The COMMITTED view, not the raw response: a purchase event that arrived after the query
|
||||
// started survives the commit as a newer overlay and must reach the gate too — otherwise a
|
||||
// just-purchased renewing sub could slip past the fail-closed double-billing check.
|
||||
// Non-IAP overlays only; untyped (unknown product) entries stay in on the safe side.
|
||||
val byToken = LinkedHashMap<String, Purchase>()
|
||||
subs.forEach { byToken[it.purchaseToken] = it }
|
||||
committed.overlay
|
||||
.filter { it.type != Sku.Type.IAP }
|
||||
.forEach { byToken[it.purchase.purchaseToken] = it.purchase }
|
||||
byToken.values.sortedByDescending { it.purchaseTime }
|
||||
}
|
||||
suspend fun acknowledgePurchase(purchase: Purchase): BillingResult {
|
||||
val ack = AcknowledgePurchaseParams.newBuilder().apply {
|
||||
setPurchaseToken(purchase.purchaseToken)
|
||||
@@ -529,16 +535,22 @@ class BillingConnection(
|
||||
OurSku.PRO_SKUS.singleOrNull { it.id == productId }?.type
|
||||
}
|
||||
|
||||
// Combines the two product-type query results: a purchase found by either type is
|
||||
// authoritative; an error is only propagated when nothing was found, so callers can tell
|
||||
// "not owned" apart from "couldn't verify one product type". Treating any found purchase
|
||||
// as authoritative is safe because every product this app sells is a Pro SKU (see
|
||||
// OurSku.PRO_SKUS). Pure and unit-tested.
|
||||
// Combines the two product-type query results: an error is only propagated when the refresh
|
||||
// learned nothing usable, so callers can tell "not owned" apart from "couldn't verify one
|
||||
// product type". A PURCHASED result of ANY product suppresses the error — every product
|
||||
// this app sells is a Pro SKU (see OurSku.PRO_SKUS), so it is by construction relevant. A
|
||||
// PENDING result only counts when it maps to a KNOWN Pro SKU: it grants nothing, and an
|
||||
// unknown pending product says nothing about the type whose query failed, so treating it
|
||||
// as a find would swallow a real "couldn't verify". Pure and unit-tested.
|
||||
internal fun combinePurchaseResults(
|
||||
iap: Result<Collection<Purchase>>,
|
||||
sub: Result<Collection<Purchase>>,
|
||||
typeOf: (String) -> Sku.Type? = DEFAULT_SKU_TYPE_RESOLVER,
|
||||
): Collection<Purchase> {
|
||||
val found = iap.getOrNull().orEmpty() + sub.getOrNull().orEmpty()
|
||||
val returned = iap.getOrNull().orEmpty() + sub.getOrNull().orEmpty()
|
||||
val found = returned.filter { purchase ->
|
||||
purchase.isPurchased || purchase.products.any { typeOf(it) != null }
|
||||
}
|
||||
return when {
|
||||
found.isNotEmpty() -> found.sortedByDescending { it.purchaseTime }
|
||||
else -> {
|
||||
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
package eu.darken.capod.common.upgrade.core.billing.work
|
||||
|
||||
import androidx.work.BackoffPolicy
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.await
|
||||
import androidx.work.workDataOf
|
||||
import eu.darken.capod.common.BuildConfigWrap
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.common.upgrade.core.billing.BillingManager
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Arms the [PurchaseAckWorker] safety net. Two triggers:
|
||||
* - a billing flow is about to launch (armed and awaited BEFORE the Play sheet, so the WorkManager
|
||||
* DB transaction lands even if the process dies around the sheet),
|
||||
* - an ack pass discovered unacknowledged purchases (called directly, pre-attempt, from
|
||||
* BillingManager's runAckPass).
|
||||
*/
|
||||
@Singleton
|
||||
class PurchaseAckScheduler @Inject constructor(
|
||||
// Resolved on the first arm, not at construction: fleet App classes eagerly inject the billing
|
||||
// stack during Application field injection, and resolving WorkManager there can trigger its
|
||||
// on-demand initialization before the Application's worker factory field is set.
|
||||
private val workManager: Provider<WorkManager>,
|
||||
) {
|
||||
|
||||
// A genuinely new flow refreshes the watch window: REPLACE the previous LAUNCH watch. The
|
||||
// worker sweeps ALL unacknowledged purchases, so replacing an older watch loses nothing — and a
|
||||
// pending rescue for an already-discovered purchase has its own identity, so starting another
|
||||
// purchase can never displace it. The long delay keeps the worker out of the window where the
|
||||
// user may still be in the Play sheet.
|
||||
suspend fun armForBillingFlowLaunch() = arm(
|
||||
name = WORK_NAME_LAUNCH,
|
||||
policy = ExistingWorkPolicy.REPLACE,
|
||||
expiresAt = System.currentTimeMillis() + BillingManager.ACK_SAFETY_NET_DEADLINE_MS,
|
||||
initialDelayMs = LAUNCH_DELAY_MS,
|
||||
)
|
||||
|
||||
// Any pending rescue already covers every unacknowledged purchase: KEEP it. Once completed
|
||||
// work exists, KEEP inserts a fresh request. Short delay — the purchase already EXISTS (unlike
|
||||
// the launch trigger), possibly for days, so waiting 30min could waste real deadline time.
|
||||
// Accepted edge of KEEP, within the rescue lane only: a pending request keeps its original
|
||||
// (possibly earlier) expiry; a newer purchase with a later deadline is only re-covered once a
|
||||
// later pass re-arms after the old work completed. Bounded residual, only reachable via
|
||||
// out-of-band purchases.
|
||||
suspend fun armForUnackedPurchases(expiresAt: Long) = arm(
|
||||
name = WORK_NAME_RESCUE,
|
||||
policy = ExistingWorkPolicy.KEEP,
|
||||
expiresAt = expiresAt,
|
||||
initialDelayMs = DISCOVERY_DELAY_MS,
|
||||
)
|
||||
|
||||
private suspend fun arm(
|
||||
name: String,
|
||||
policy: ExistingWorkPolicy,
|
||||
expiresAt: Long,
|
||||
initialDelayMs: Long,
|
||||
) {
|
||||
if (expiresAt <= System.currentTimeMillis()) {
|
||||
// Play has already voided (or is about to void) such a purchase; a sweep can't help.
|
||||
log(TAG, WARN) { "arm($policy): deadline $expiresAt already passed, not scheduling" }
|
||||
return
|
||||
}
|
||||
val request = OneTimeWorkRequestBuilder<PurchaseAckWorker>().apply {
|
||||
setConstraints(
|
||||
Constraints.Builder().apply {
|
||||
setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
}.build()
|
||||
)
|
||||
// Launch trigger: the worker must not run while the user may still be in the Play
|
||||
// sheet — an immediate sweep would find nothing unacknowledged, report success, and
|
||||
// complete the net before the purchase it exists for even happened.
|
||||
setInitialDelay(initialDelayMs, TimeUnit.MILLISECONDS)
|
||||
setBackoffCriteria(BackoffPolicy.EXPONENTIAL, BACKOFF_DELAY_MS, TimeUnit.MILLISECONDS)
|
||||
setInputData(workDataOf(PurchaseAckWorker.KEY_EXPIRES_AT to expiresAt))
|
||||
}.build()
|
||||
|
||||
// Await the enqueue: the caller arms this because the process may die at any moment — a
|
||||
// fire-and-forget enqueue could be lost with it. Cancellable and BOUNDED: every caller
|
||||
// needs a durable enqueue without an unbounded stall — a WorkManager that never settles
|
||||
// must become an exception (handled fail-open by every caller) instead of a hang (which on
|
||||
// the launch lane would park the purchase and its busy guard forever).
|
||||
val operation = workManager.get().enqueueUniqueWork(name, policy, request)
|
||||
withTimeoutOrNull(ENQUEUE_TIMEOUT_MS) { operation.await() }
|
||||
?: throw IllegalStateException("WorkManager enqueue did not settle within ${ENQUEUE_TIMEOUT_MS}ms")
|
||||
log(TAG) { "arm($policy): safety net armed, expiresAt=$expiresAt" }
|
||||
}
|
||||
|
||||
companion object {
|
||||
// WorkManager persists these names AND the worker's class name in its DB across app
|
||||
// updates: keep all of them stable while old work may exist (hence the version suffix for
|
||||
// future changes). Separate identities per trigger: the launch watch's REPLACE must not be
|
||||
// able to displace a pending rescue for a purchase that already exists.
|
||||
private val WORK_NAME_LAUNCH = "${BuildConfigWrap.APPLICATION_ID}.gplay.purchase-ack.launch.v1"
|
||||
private val WORK_NAME_RESCUE = "${BuildConfigWrap.APPLICATION_ID}.gplay.purchase-ack.rescue.v1"
|
||||
|
||||
private const val LAUNCH_DELAY_MS = 30 * 60 * 1000L
|
||||
private const val DISCOVERY_DELAY_MS = 60 * 1000L
|
||||
private const val BACKOFF_DELAY_MS = 30 * 60 * 1000L
|
||||
private const val ENQUEUE_TIMEOUT_MS = 10 * 1000L
|
||||
|
||||
val TAG: String = logTag("Upgrade", "Gplay", "Billing", "AckScheduler")
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
package eu.darken.capod.common.upgrade.core.billing.work
|
||||
|
||||
import android.content.Context
|
||||
import androidx.hilt.work.HiltWorker
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ListenableWorker.Result
|
||||
import androidx.work.WorkerParameters
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.INFO
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.common.upgrade.core.billing.BillingManager
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
|
||||
/**
|
||||
* Persistent acknowledgement safety net, armed by [PurchaseAckScheduler].
|
||||
*
|
||||
* Play auto-refunds (and revokes) any purchase not acknowledged within 3 days. The in-process ack
|
||||
* machinery in [BillingManager] handles every case where the process lives long enough — this
|
||||
* worker covers the case it can't: the process dies around the Play purchase sheet (OEM task
|
||||
* killers) and the user doesn't reopen the app before the deadline. Play voids such purchases and
|
||||
* revokes the entitlement, so the user loses what they signed up for.
|
||||
*
|
||||
* Self-completing by design: nothing cancels this work from the foreground ack path (an ack pass
|
||||
* can legitimately see zero unacknowledged purchases while the Play sheet is still open, which
|
||||
* must not tear down the net). The redundant sweep after a successful foreground ack is one
|
||||
* purchase query.
|
||||
*/
|
||||
@HiltWorker
|
||||
class PurchaseAckWorker @AssistedInject constructor(
|
||||
@Assisted private val context: Context,
|
||||
@Assisted private val params: WorkerParameters,
|
||||
private val billingManager: BillingManager,
|
||||
) : CoroutineWorker(context, params) {
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
val expiresAt = inputData.getLong(KEY_EXPIRES_AT, 0L)
|
||||
log(TAG) { "doWork(): attempt=$runAttemptCount, expiresAt=$expiresAt" }
|
||||
|
||||
if (!isWorthSweeping(System.currentTimeMillis(), expiresAt)) {
|
||||
// Past Play's refund deadline (or malformed input): retrying can't achieve anything.
|
||||
// failure() is deliberate over success() — it is visible in WorkManager diagnostics,
|
||||
// and a completed state lets a later KEEP enqueue insert fresh work.
|
||||
log(TAG, WARN) { "doWork(): deadline passed, giving up" }
|
||||
return Result.failure()
|
||||
}
|
||||
|
||||
// Bounded well below WorkManager's 10-minute execution limit, but generous enough for the
|
||||
// connection wait plus the per-purchase inline retries. A sweep that ran out of time is a
|
||||
// transient outcome, not a verdict. External cancellation propagates out of doWork — it
|
||||
// must never be converted into success.
|
||||
val sweep = withTimeoutOrNull(SWEEP_TIMEOUT_MS) {
|
||||
billingManager.ensureAllAcknowledged()
|
||||
}
|
||||
log(TAG, INFO) { "doWork(): sweep=$sweep" }
|
||||
|
||||
return mapSweep(sweep, System.currentTimeMillis(), expiresAt)
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Persisted in WorkManager's request data — keep the key stable while old work may exist.
|
||||
const val KEY_EXPIRES_AT = "purchase.ack.expiresAt"
|
||||
|
||||
private const val SWEEP_TIMEOUT_MS = 4 * 60 * 1000L
|
||||
|
||||
// Pure so the retry/expiry decision is unit-testable without a WorkManager test harness.
|
||||
internal fun isWorthSweeping(now: Long, expiresAt: Long): Boolean =
|
||||
expiresAt > 0L && now < expiresAt
|
||||
|
||||
internal fun mapSweep(
|
||||
sweep: BillingManager.AckSweepResult?,
|
||||
now: Long,
|
||||
expiresAt: Long,
|
||||
): Result = when (sweep) {
|
||||
BillingManager.AckSweepResult.COMPLETE -> Result.success()
|
||||
BillingManager.AckSweepResult.PERMANENT_FAILURE -> Result.failure()
|
||||
// RETRY or timeout (null): keep trying until the deadline. WorkManager's exponential
|
||||
// backoff caps at 5h, so the 3-day window still yields many attempts.
|
||||
else -> if (now < expiresAt) Result.retry() else Result.failure()
|
||||
}
|
||||
|
||||
val TAG: String = logTag("Upgrade", "Gplay", "Billing", "AckWorker")
|
||||
}
|
||||
}
|
||||
@@ -13,5 +13,14 @@ sealed class UpgradeEvents {
|
||||
*/
|
||||
data object RestoreInconclusive : UpgradeEvents()
|
||||
data object SubscriptionStillRenewing : UpgradeEvents()
|
||||
data object SubscriptionCheckFailed : UpgradeEvents()
|
||||
|
||||
/**
|
||||
* Play answered, no completed purchase exists, but a payment is still being processed. Purely
|
||||
* informational: nothing to fix, nothing to restore — Pro unlocks by itself once Play clears
|
||||
* the payment, and a new purchase would be rejected (or double-charge) in the meantime.
|
||||
*/
|
||||
data object PurchasePending : UpgradeEvents()
|
||||
|
||||
/** The pre-purchase check with Play didn't finish, so the purchase was not started. */
|
||||
data object PurchaseCheckFailed : UpgradeEvents()
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user