Compare commits

...
75 Commits
Author SHA1 Message Date
d4rken-org-releaser[bot] 8a57257f03 Release: 5.2.6-rc0 2026-09-10 14:31:40 +00:00
darken fc4d2674e9 General: Update app translations from Crowdin 2026-09-10 16:13:30 +02:00
darken 1a176e3df8 chore: Remove retired Claude Cafe plugin settings 2026-09-08 18:27:44 +02:00
Matthias Urhahn 1fff40b9d4 Merge pull request #707 from d4rken-org/chore/clanker-cafe-plugins
General: Move project assistant plugins to Clanker Cafe
2026-09-07 13:12:59 +02:00
darken 9a9d4452d1 chore: Migrate Claude plugins to Clanker Cafe
Enable the six migrated plugins at project scope and retain explicit disabled entries for the old marketplace. The local DebugBadger pilot has been promoted to a project installation.

Validation: a fresh Claude Code session loaded all six new plugin identities, their commands and agents, the migrated hooks and all four MCP services. Read-only schema discovery passed without device actions or service writes.
2026-09-07 13:06:08 +02:00
darken 43740d8b2e chore(build): Enable R8 obfuscation for the Google Play flavor
Google Play scores the obfuscation share of uploaded bundles and flags
listings below its threshold ("App optimisation is below our threshold",
fix by Feb 2027). Minify and shrink were already on; the shared
-dontobfuscate was the only thing keeping the score at 0%.

-dontobfuscate moves to a FOSS-only rule file; the Play rule file keeps
SourceFile/LineNumberTable for retracing and pins names only where they
reach users or logs: AapSetting/AapCommand subclasses (session logs),
ViewModel1 subclasses (log tag), Throwables (error dialog label). All
reflective targets (BuildConfig, ArtMirror, NeverCall, InvokeStub,
AncModeActionCallback) already carry @Keep or explicit keeps and are
identity-mapped in the gplayRelease mapping. The release workflow
archives the mapping next to the Play upload.
2026-09-06 10:41:37 +02:00
darken 83849efe80 refactor(logging): Replace receiver-derived log tags with explicit tags
The Any.log overload built its tag from this::class.java.name, which turns
into CAP:a once the class is obfuscated. Every call site now passes a
logTag(...) constant and the overload is gone so it cannot be reintroduced.
2026-09-06 10:41:37 +02:00
darken ed66731207 chore(fastlane): Roll out beta track releases to 100%
Both supply lanes staged uploads at 10% of the beta track, which left the
remaining 90% waiting on a manual Play Console rollout step for every
release. Beta-track users have already opted in, so staging within that
audience adds a manual gate without adding safety.

Promotion from the beta track to production stays manual.
2026-09-03 18:20:01 +02:00
d4rken-org-releaser[bot] a41bef21b5 Release: 5.2.5-rc0 2026-09-03 11:25:44 +00:00
darken 91b49536b7 chore: Disable kotlin-lsp plugin 2026-09-03 13:10:25 +02:00
darken 8690072ee2 General: Update app translations from Crowdin 2026-09-03 13:10:25 +02:00
darken 92c829c7e6 General: Update fastlane translations from Crowdin 2026-09-03 13:10:25 +02:00
Matthias Urhahn 1c67a78130 Merge pull request #703 from d4rken-org/docs/crowdin-context-ownership
General: Note that translator context is edited on Crowdin
2026-09-03 11:09:08 +02:00
darken 7a64a23d1e docs: Record that translator context lives on Crowdin
Every string in the three Android source files now carries a custom
context written on Crowdin. A custom value is sticky per string: Crowdin
stops re-deriving that string's context from its XML comment on later
source pushes, so editing the comment in values/strings.xml silently
reaches no one.

Nothing in the tree recorded that, which makes the next comment edit a
trap. The rule file is where a reader lands when they touch strings.xml,
so it says where context is edited now.
2026-09-03 11:02:58 +02:00
Matthias Urhahn e2fcbbfdfd Merge pull request #702 from d4rken-org/worktree-remove-dead-strings
General: Stop asking translators for strings the app no longer uses
2026-09-03 10:59:33 +02:00
darken 29639cdcd1 chore(strings): Remove 49 unused string resources
None of these are referenced from any source set: a fixed-string grep over
app/src (Kotlin, Java, XML including the manifest, excluding res/values*)
finds zero hits for each, and the app has no getIdentifier-style dynamic
resource lookup, so nothing can reach them by name at runtime.

43 lost their last usage in an identifiable commit, mostly the Compose
migration (63692595), the upgrade/billing rework (c072b908, 3651bb3d,
e35bce4d, abb189f4) and the device-settings card grouping (65a74e9b,
32ff7e5b, 8a3a6cad). The other 6 were never referenced at all after the
commit that added them: debug_debuglog_recording_progress,
troubleshooter_ble_result_failure_body,
profilessettings_maindevice_identitykey_description,
profiles_maindevice_encryptionkey_description, pods_dual_left_short_label
and pods_dual_right_short_label.

Removed from the three base files and from all 75 locale files each, so a
Crowdin pull does not reintroduce them.
2026-09-03 00:22:11 +02:00
darken d8a6ef2286 feat(monitor): Show remaining case charges in the notification
The case row in the expanded notification carried a percentage alone, which
doesn't answer whether the case can still top the earbuds up. It now appends
the count the overview card already computes, in the same " . <value>" shape
the earbud rows use for their time estimates.

The figure is shown as a fraction rather than the card's integer because the
integer barely moves on a small case: AirPods Pro 3 publishes a 2.0 charge
case, so the count only ever reads 0, 1 or 2, and everything below 50% reads
0. A notification suffix makes no adequacy claim, so it can report the number
without the interval logic the coloured card line needs.

The decimal is cut rather than rounded. A 4.0 case at 24% covers [0.96, 1.00),
which the card calls short of a full charge, and rounding to the nearest tenth
would print "1.0" beside that. Cutting also keeps the lower-bound wording
honest, since 3.8 at 20% is 0.76 and may not claim "0.8+". The epsilon before
the cut absorbs binary float error only: 2.0 at 35% lands on 0.69999999 and
still has to read 0.7.

An empty case drops the "+" even on a lower-bound spec, matching the card's
treatment of zero as exact rather than as an underestimate.

Apple's Optimized Charge Limit is deliberately not modelled. It pauses around
80% only when the routine predicts you won't need the pods yet, so it applies
to some top-ups and not others, and the unit stays whole pair charges.

The case label gets a width cap because the row's progress bar is its only
weighted child. The added word is translatable, and a long rendering at a
large font scale would otherwise take the row and leave the bar at zero width.
2026-09-02 22:05:16 +02:00
darken e68927595c chore: Update default Play Store release notes and translations 2026-09-02 20:25:41 +02:00
darken aa928de265 General: Update app translations from Crowdin 2026-09-02 10:31:46 +02:00
darken 3662904d75 fix(debug): Make the recorder the only writer of the debug flag
The module's flag collector was pure redundancy: State.recorder is only
ever written right after a Recorder.start()/stop() that already published
the same value. Its one added behaviour was a hazard - a late-delivered
committed stop could overwrite the flag a newer session's start() had just
set, blanking diagnostics for up to the five seconds the header read is
bounded at.

Recorder.start()/stop() stay the single writers, next to the file logger
they install.

The collector's test goes with it: it hard-coded a two-collector queue
depth that can no longer be reached. In its place, a test asserting the
flag against the recorder itself, and one that forces the late-stop
delivery through a hand-stepped app-scope dispatcher.

Fixes review findings F2, F3, F4
2026-09-01 10:11:01 +02:00
darken cec36268e0 fix(debug): Keep the flag collector off uncommitted recorder states
The collector mirrored isRecording from every state emission, including the
start and stop requests, whose value the recorder has already moved past. On a
loaded device that write can land after Recorder.start()'s, leaving isDebug
false for the whole header-read window (5s) while the file logger is live.

distinctUntilChangedBy collapses the initial state and the start request into
one emission, drop(1) removes it, so only committed transitions are published.
The operator order matters: drop(1) first would let the start request through.

Fixes review finding F1.
2026-09-01 10:11:01 +02:00
darken 5895ccbc27 fix(tests): Match only power snapshot lines in the log filter 2026-09-01 10:11:01 +02:00
darken 798ac32ec9 fix(tests): Pin the new Robolectric tests to SDK 33 2026-09-01 10:11:01 +02:00
darken 6ed0bf8257 feat(debug): Log the device power state while recording
A reception blackout in a capture is as easily the display going off as
a broken scan, and nothing in a capture currently says which. The full
snapshot is written on every screen, doze and power-save broadcast, plus
once when the recording starts, so a single line format answers both
"what is it now" and "what just changed".

The receiver is registered before the first snapshot is taken:
ACTION_SCREEN_OFF is not sticky, so a screen-off in between would appear
as neither a transition nor a corrected state.

The catch sits on the inner receiver flow rather than after
flatMapLatest. Flow.catch completes the flow it is applied to, so a
top-level catch would end the recording-flag collection on the first
failure and every later recording in the process would carry no power
state at all.

Nothing here may throw: onReceive is an Android callback outside any
flow, so a vendor PowerManager that throws would take the process down.
Each field is guarded individually so a bad read costs one value rather
than the line.
2026-09-01 10:11:01 +02:00
darken 39a6c3c89f feat(device): Log the effective BLE scan configuration
A capture that shows no scan results cannot currently answer whether the
scan was filtered. The configuration is decided once when the scan
starts, which is usually long before the recording that is meant to
diagnose it, so the line is re-emitted when a recording begins while the
scan is already running.

The re-emission drops against the value captured at the first emission
instead of drop(1): launchIn subscribes asynchronously, so a StateFlow
replays whatever is current at subscription time. A recording started in
that gap would be swallowed as if it were the initial value, which is
exactly the case the line exists for.

filterPolicy is a parameter rather than something derived from the
filter set, because the unfiltered mode is implemented as a single
match-all filter. A count-based summary would report it as a filtered
scan, inverting the answer.

"Requested" and not "filtering"/"batching": adapter capability plus our
own setting is what we asked the platform for, not proof that the
controller offloaded anything.
2026-09-01 10:11:01 +02:00
darken 2ba90e1d18 feat(debug): Flip the debug flag when the recorder starts writing
The flag was only written from committed recorder module state, which
publishes after the recording header has been read. That read is bounded
at 5s, so for up to five seconds the file logger is live while the flag
still says false, and diagnostics keyed off it are missing from exactly
the window a reporter uses to reproduce a screen-off issue.

The module-state writer stays: it covers a resumed session and any path
that reaches isRecording=false without going through Recorder.stop().
A rolled-back start self-corrects, because the rollback stops the
recorder before publishing the failure.

BaseTest resets the flag per test instance because it is JVM-global. It
goes into init rather than the companion teardown, which uses JUnit 5's
@AfterAll and never fires under the JUnit 4 Robolectric runner.
2026-09-01 10:11:01 +02:00
darken 92dac661c4 fix(device): Keep a log line for rejected malformed addresses
Structurally broken addresses now fail the octet parse instead of
throwing, so the catch-all error log no longer fires for them. Log the
rejection at WARN with a redacted address so support logs still show it.

Fixes review finding F1.
2026-08-31 15:54:51 +02:00
darken 7712b18388 fix(monitor): Re-arm the teardown when a start request is short-circuited
In AUTOMATIC mode with nothing connected the session is torn down 15 seconds
after the state that said so, and only a new state emission cancels that. A
start request arriving while a session is live is acknowledged and returns
without touching the pending teardown, and the connection state that would
abort it lags the Bluetooth event that caused the start by roughly 0.75s. A
request landing in the tail of the window was therefore answered with
"keeping current session" and the session went down anyway, taking every
reaction with it — in one recording the popup reaction was down for 34
seconds spanning an entire lid cycle.

The short-circuit now bumps a start signal that the mode pipeline combines
in, so the bump cancels the pending inner flow through the existing
cancellation topology and arms a fresh window. The countdown also re-reads
the signal after its delay, which closes the case where the bump lands while
an expired countdown is already unwinding. A re-armed window runs 15 seconds
from the start request, so the total dwell can exceed 15 seconds: the request
is fresh evidence of activity.

The decision segment moves to a top-level internal function so it can be
driven directly in tests, following MonitorModeState and
buildMonitorModeState which are top-level for the same reason.
2026-08-31 15:54:51 +02:00
darken d7988189a0 fix(reaction): Stamp connect times from the ACL broadcast
The connection timestamp cache was only maintained while the connected-devices
flow had a subscriber. If the process died while a device stayed connected and
that device then disconnected and reconnected with nothing running, the
restarted collection found the old entry still keyed by a currently-connected
address, kept it through the prune, and reported the original connect time —
so the popup age check rejected an arbitrarily old connection.

The ACL broadcasts arrive at a manifest-registered receiver that wakes the
process regardless of any flow subscription, so the stamp is taken there
instead: connect stamps (keeping an existing one, which is the earlier and
therefore truer time), disconnect drops the entry. The flow keeps its own
stamp-on-first-sight and prune as a backstop for the force-stopped state and
missed broadcasts.

ACL_DISCONNECTED was already in the receiver's expected actions but was never
registered in the manifest. It does not start the monitor: a disconnect is not
a reason to start monitoring, and the start triggers here are deliberately
conservative.
2026-08-31 15:54:51 +02:00
darken ff5d0cedfa fix(reaction): Stop bonded-device queries from poisoning connect times
seenFirstAt on a connected device is meant to be the connect time, and the
connected-devices flow maintains that by pruning its cache to the currently
connected addresses on every emission. bondedDevices() wrote into the same
cache for every bonded device, connected or not, and never pruned.

AapAutoConnect queries bonded devices on every connected-devices emission,
the disconnect one included, so an entry pruned at disconnect was re-stamped
milliseconds later at disconnect time. The next reconnect then inherited the
previous disconnect as its connect time, and the popup reaction rejects a
connection older than 30 seconds — so any reconnect more than half a minute
after the previous disconnect silently lost its popup.

bondedDevices() is now a cache reader: a connected bonded device still
reports its true connect time, a non-connected one gets the current time,
which no caller reads. The connected-devices path becomes the cache's only
writer, which is the invariant its prune-and-stamp logic already assumed.
2026-08-31 15:54:51 +02:00
darken f82adeba42 fix(monitor): Persist AAP keys only when their content changed
AppleDeviceProfile is a data class whose key fields are ByteArrays, so the
generated equals() compares them by reference and the "did anything change"
guard was true on every key exchange. That meant a redundant profile write on
every connect and a "Persisted keys" line that said nothing about whether a
key had actually changed.

Compare content instead, per key, and name the key that changed in the log.
2026-08-31 15:54:51 +02:00
darken cc99ad59ab fix(device): Resolve identity keys against reversed address octets
On at least one vendor stack the address handed up by the BLE scan callback
and the identity key stored for a profile disagree on octet order: the key
resolves the address only when its octets are reversed. Identity resolution
then fails on every advertisement, so no frame is attributed to the profile
and the case popup, connection popup, encrypted 1% battery granularity and
session reconnection all go with it.

RPAChecker gains resolve(), which reports the order that resolved. The
standard-order attempt is unchanged and ungated, so no currently-resolving
device can start failing. The reversed attempt only runs when the reversed
form carries the resolvable-private-address type marker (top bits 01), which
skips roughly three quarters of the extra comparisons for generic random
addresses. verify() is now a thin wrapper over resolve().

Address parsing is validated explicitly: exactly six components, each in
0..255. A seven-component string used to be silently mis-sliced, and
"15A:..." truncated to a valid octet — both cases could resolve.

The failure log no longer prints the identity key. A malformed address or
key wrote an identity-tracking secret into exactly the debug logs users mail
to support; the event and its level stay, the key is reduced to its length
and the address goes through redactedForLogs().

The history lookup that recovers a device by key now skips candidates bound
to a different profile. A 24-bit forward collision could already select the
wrong history; attempting two orders roughly doubles that exposure.

Test vectors are synthetic and derived from the key already committed in
RPACheckerTest. Two of them are complementary: one has an RPA-shaped
reversed form that fails the hash (proving the comparison runs), the other
has a reversed form that resolves cryptographically but carries marker 00
(proving the gate runs).
2026-08-31 15:54:51 +02:00
Matthias Urhahn b66c756741 Merge pull request #695 from d4rken-org/worktree-case-charges-align
General: Tidy up the captions under the battery levels
2026-08-31 06:34:50 +02:00
darken fbbdb4e131 ui(overview): Give the last known caption more room above
At 4.dp the caption crowded the bottom edge of the battery surface it
describes. 8.dp separates the two without pushing it far enough to read as
its own block, which 12.dp (the gap used before the ANC selector) would.
Both pod cards carry the caption, so both move together.
2026-08-30 08:12:23 +02:00
darken 0bf6bf7b5f ui(overview): Center the case charges line in the battery card
The line sat at a fixed 36.dp start indent, which put its left edge under
the case percentage label (28.dp icon + 8.dp spacer). That follows the M3
"supporting text aligns with the item's text column" convention, but the
case row is not a list item, and the sentence runs nearly full width, so
the lone left gutter read as an accident rather than an alignment.

Centering on the capsule instead would have been the other candidate, but
the capsule is weight(1f) between the percentage label and the status-chip
FlowRow, so its x-offset and width are both runtime values that vary with
locale and font scale. No static padding reaches it; it would take
onGloballyPositioned or a custom Layout, which is not worth it for a
caption. Centering in the surface is stable and needs neither.
2026-08-30 08:12:15 +02:00
darken 865a609203 fix(ui): Keep the device detail sheet reachable while scrolled
The detail sheet was composed inside the device-info lazy item, so tapping the
battery runtime warning banner after that item had scrolled out of composition
did nothing visible while still setting the visibility flag, which made the
sheet pop up unprompted on scrolling back up. Build the detail items and compose
the sheet at screen scope instead; the card now only reports the tap.

Fixes review finding F1
2026-08-25 23:42:23 +02:00
darken 9f213cf499 ui(settings): Reframe battery health as estimated listening time
The figure compares observed listening drain against Apple's rated hours, so
it cannot separate a degraded cell from loud volume, cold weather or a hungry
codec. Runtime wording is true either way, "Battery Health" is not.

String keys are kept as they are so the translated locale files aren't
orphaned.
2026-08-25 23:42:23 +02:00
darken 1330c77ed8 feat(battery): Warn when a pod's listening time drops by half
The per-pod listening-time estimate was only visible behind the info icon on
the device settings header card. A pod that reaches half its rated listening
hours or less now raises a banner on the device settings screen, tapping it
opens the same detail sheet.

A displayed number needs less backing than one that raises a warning, so a
reading is only promoted into the banner when its slot has accumulated at
least 8 listening sessions across its qualifying rates and the newest of
those rates is at most 60 days old. The banner reads the already-gated state
field, so the per-profile battery estimate toggle suppresses it too.

The detail sheet's visibility moves out of DeviceInfoCard so both the info
icon and the banner can open it.
2026-08-25 23:42:23 +02:00
darken 2356726417 fix(overview): Call an empty case not enough on older models
AirPods Gen 1 and Gen 2 publish their case capacity as a lower bound, and
the adequacy check let that guard run before any zero-reading check. At a
0% case those two models rendered the definite "no charges left" text in
the neutral uncertainty colour while the screen reader hedged with "may
not be enough", contradicting the same node three ways.

The empty reading is now handled before the lower-bound guard. It sits
after the "enough" branch, which a zero reading can never satisfy, so it
cannot mask a positive claim. The lower-bound rule exists to avoid
underselling a case whose published capacity is only a floor, and an
empty case has nothing to undersell.

Fixes review finding F4
2026-08-25 22:33:42 +02:00
darken 03c09917da fix(overview): Stop denying a charge the reading still allows
The visible count came from the pessimistic end of the interval alone, so
an interval straddling a full charge said "Less than one more full charge"
while the colour stayed neutral and TalkBack said "may not be enough". An
uncertain interval now rounds to a single charge instead, and an
open-ended spec that has not reached one charge names no number at all.

Fixes review finding F2
2026-08-25 22:33:42 +02:00
darken 70830cf2ca fix(overview): Call a case exactly one charge short "not enough"
The reading is an interval that excludes its upper end, so an upper end
landing exactly on one full charge still leaves every reachable value
below one. AirPods Gen 4 at 10% and Pro 3 at 40% read neutral instead of
orange because of it.

Fixes review finding F1
2026-08-25 22:33:42 +02:00
darken 43140005f2 chore(overview): Sort the imports added for the case charges line 2026-08-25 22:33:42 +02:00
darken 59e2a16a39 test(overview): Cover the battery level states and the case charges line
Colour is not exposed through the semantics tree, and captureToImage() times
out under Robolectric, so the rendered result is pinned in two halves: which
tier each of the five slots reports, and which colour a tier resolves to per
theme mode. The theme test drives an in-app dark override on a light host,
which is the case a plain isSystemInDarkTheme() read would get wrong.

The case charges line is covered for presence, the three adequacy states, both
special renderings, and under RTL, a narrow card and a doubled font scale.
2026-08-25 22:33:42 +02:00
darken 11d6ddf3fa feat(overview): Say how many earbud charges the case still holds
The case percentage alone doesn't answer the question people actually have,
which is whether the case can top the earbuds up again. The card now says so in
words, below the case row.

The reading is treated as the interval it really is: a decile source at 20% on
a 4.0-charge case means 0.8 to 1.2 charges, which is not an answer, so the line
stays neutral instead of flipping colour as the reading bounces between
adjacent frames. Only an interval that lies wholly above or below one full
charge is coloured, which removes the need to remember anything between frames.

The line is stacked rather than inline: the case row already has four children
with only the capsule weighted, so a plural in front of it would collapse the
capsule at large font scales and in locales with long plural forms.
2026-08-25 22:33:42 +02:00
darken b10b42eede feat(monitor): Expose how coarse a case battery reading is
PodDevice.batteryCase merges an AAP notification, the encrypted advertisement
payload, the public advertisement nibble and the cache into one Float, and the
provenance is gone by the time anyone reads it. The public nibble only carries
deciles, so 20% there can mean anything up to 29%.

batteryCaseReading walks the same precedence and keeps the step size of the
source that won, leaving batteryCase untouched for every existing caller.
2026-08-25 22:33:42 +02:00
darken 5801b8ea56 feat(device): Record how many earbud charges a case holds
Derived per model as hoursWithCase / hoursListening - 1, with both figures read
under the same noise control condition Apple quotes them under, matching their
stated test method (drained AirPods recharged to 100% and playback resumed
until both the AirPods and the case were fully discharged).

Models whose published total reads "more than N hours" are marked as a lower
bound so nothing downstream can turn an unbounded figure into a confident
claim. AirPods Pro is deliberately left without a spec: its 4.5 h ANC listening
figure is verified but the matching with-case total is not, and an unsourced
number is worse than no line at all.

The spec gates on itself rather than on hasCase — twenty models set hasCase,
including Beats Solo Buds, whose case holds no battery.
2026-08-25 22:33:42 +02:00
darken b9461d3259 feat(a11y): Announce a low battery instead of only colouring it
Crossing the warn or critical threshold was expressed as a colour change only,
so it did not exist for a screen reader. Each battery slot on the overview card
now carries the level as a state description.
2026-08-25 22:33:42 +02:00
darken 7960144be6 ui(overview): Give a low battery a colour that means "low"
The warn band was drawn in colorScheme.tertiary, which is whatever the palette
seed produces: olive under the amber theme, where the healthy band (primary) is
burnt orange, so a warning read as decoration and healthy read as a warning.

The warn band now uses fixed light/dark tokens, and the percentage text is
tinted at warn and critical instead of the level living in the gauge alone.
Values are derived against the composited backgrounds the cards actually draw
on: the gauge Surface at 4dp tonal elevation, and the same surface at alpha 0.7
that a card without live data uses. Measuring against raw surface would have
admitted values below 3:1 on screen.

The tokens are resolved from the theme mode CapodTheme already computed, not
from isSystemInDarkTheme(), so an in-app dark override on a light system does
not pick the light tokens.
2026-08-25 22:33:42 +02:00
darken 0f95538f39 feat(battery): Add a shared battery tier for level colours
The overview duplicates the same 30%/15% thresholds in four places, with two
subtly different unknown checks: the capsule and the pod gauge accept any
percent >= 0f, so Float.POSITIVE_INFINITY renders as a full, healthy ring.
batteryTier() settles that on the finite check the other two already use.
2026-08-25 22:33:42 +02:00
darken fec512175b fix: Prevent crash in Amharic battery time formatting
The Amharic translation of battery_time_remaining_format_hm dropped the
conversion character from its second placeholder: "%1$dሰ %2$ደ" instead of
"%1$dሰ %2$dደ". Java's formatter reads "%2$" followed by "ደ" as an unknown
conversion and throws UnknownFormatConversionException.

formatBatteryDurationShort() takes that branch whenever an estimate has both
hours and minutes, so any duration between 1h1m and 23h59m threw. It is
reached from the dashboard pod cards, the home screen widget, and the
foreground service notification, which makes it a crash on the common path
rather than an edge case.

Found because the Play Store rejected the Amharic screenshot upload: the
widget configuration preview had rendered as a 1x1 image, the layoutlib
render having aborted on the same exception.

Corrected at source in Crowdin, and in CAPod's translation memory, which
still mapped the English source to the broken string at 100% match and would
have re-injected it on the next pre-translate run.
2026-08-25 21:52:33 +02:00
darken a427fcbf4e chore(screenshots): Commit only English Play Store screenshots
Only en-US screenshots are tracked from now on. The other 67 locales are
generated on demand and gitignored, which drops 35 PNGs and roughly 5 MB from
the repository and stops every screenshot refresh from churning binaries in
five languages nobody reviews.

Play Store still gets the full 68 locales. supply only uploads what is present
under fastlane/metadata/android/<locale>/images/phoneScreenshots/ and retains
whatever was last pushed for locales absent from an upload, so localization is
maintained by an occasional manual regen plus screenshots_only rather than by
every PR.

PlayStoreLocales.kt had been committed since February holding a truncated
four-locale batch slice, left behind when a run was killed before its EXIT trap
restored the file. The next run then copied that slice over its own backup and
faithfully restored the corruption, which is why the remnant survived six
months. It is replaced with a documented en-US placeholder, the generator no
longer emits an annotation nothing references, and the script now refuses to
start when a stale .bak is present instead of clobbering the only good copy.

The refresh runbook is corrected as well: it ended with a checkout that restores
tracked files from the index, which would have reverted the freshly rendered
English screenshots while the store received them, and its upload step is now
gated so a failed upload leaves them staged for a retry rather than committed as
though deployed.
2026-08-25 20:08:48 +02:00
darken c49cd62b2a chore: Rename support-investigator plugin entry to support 2026-08-25 20:08:48 +02:00
d4rken-org-releaser[bot] abf28f335c Release: 5.2.4-rc0 2026-08-22 04:42:29 +00:00
Matthias Urhahn 540e6d1dbc Merge pull request #690 from d4rken-org/crowdin-update-20260821
General: Update translations from Crowdin
2026-08-21 20:54:10 +02:00
darken 76e1128e93 General: Update app translations from Crowdin 2026-08-21 19:14:22 +02:00
darken cbf681ccfc General: Update fastlane translations from Crowdin 2026-08-21 18:56:35 +02:00
darken 30f17b4791 fix(ui): Drop the removed monitor mode setting from the auto connect hint
The auto connect description told users the toggle would set the monitor mode
setting to 'Always'. That setting was removed in 5.1.6 and the mode is derived
from profile state now, so the sentence pointed at a settings entry that no
longer exists. The behaviour it described is unchanged: enabling auto connect
still resolves to continuous monitoring.

Base string only, so Crowdin flags the existing translations as outdated.
2026-08-21 10:52:55 +02:00
Matthias Urhahn cd6aaaf165 Merge pull request #687 from d4rken-org/worktree-settings-plain-language
General: Explain the compatibility settings in plain language
2026-08-21 01:20:03 +02:00
darken 289dcd660e ui(settings): Explain compatibility options in plain language
The three compatibility toggles described their own mechanism ("Don't
delegate data filtering to the system", "broadcast instead of callback")
without saying which symptom they address, so a non-technical user had no
way to tell whether a row applied to them. Each summary now leads with the
symptom and keeps a plain description of what changes. Titles are
unchanged: they name the mechanism and are referenced verbatim in issue
replies.

The category description "Don't touch if everything works ;)" stopped
rendering in 63692595 when the res/xml preference screens were replaced by
Compose, since SettingsCategoryHeader only took a title. The string and its
~75 translations survived unused. Added an optional subtitle to the header
and wired it back up.

Also rewrote the extra-notification summary, which named the "Device
status" channel but never said where to find it.

Reported via a Play Store review (vi, 4 stars, 5.2.3-rc0).
2026-08-21 01:13:13 +02:00
Matthias Urhahn b07428f894 Merge pull request #686 from d4rken-org/worktree-anc-adaptive-misreport
Device: Fix duplicate listening mode commands and silent failures
2026-08-20 18:16:20 +02:00
darken dbd1d422c6 refactor(aap): Split the echo classifier out of this change
Everything that reinterprets what the device reported moves to its own branch
(anc-echo-classifier), leaving only changes that stand on their own.

The classifier addresses a fault that has never been observed being handled: it
reproduced on two of four sessions and none since it was written. It also adds a
failure mode that did not exist before, where a report misattributed to our own
write makes CAPod show a mode the device is not in. That is a poor trade to carry
into main on the strength of tests alone, so it waits until it can be seen working
against a live fault.

What remains does not depend on the misreport:

- the verification deadline was 1000ms while the device answers in 833-1008ms, so
  a healthy reply could land just after the timer and trigger a bogus divergence
  plus a redundant re-send; this was captured live
- a listening mode request the device did not confirm produced no feedback at all
  for any mode except Off, which was a gap in the event plumbing rather than a
  timing artifact
- a mode outside the device's listening mode cycle was rendered as an ordinary
  selectable button whenever it happened to be the current mode
- an Off report arriving while a different mode was requested could teach the Allow
  Off inference, persisting "Off is permitted" into the device profile

Also drops effectiveAncMode, which only had an effect while the classifier was
present.
2026-08-20 12:38:30 +02:00
darken 0b83e86c5e fix(aap): Correct an overstated safety claim and a test that proved nothing
Review follow-ups on the previous commit.

Its claim that "nothing is learned or persisted" was wrong. BatteryEstimator
buckets drain samples by the current listening mode and force-persists the
accumulated window whenever that mode changes, so a misattributed mode can write
a drain rate to disk under the wrong bucket, and the corrective report does not
remove it. This is not new: the misreport being fixed here already mis-buckets in
the same way, and more often, since the device claims OFF while the pods play
Adaptive. Classifying corrects the common case and only gets it wrong on the rarer
misattribution. The design stands, the claim does not.

Two other claims were also too strong. The recorded echo is the first report after
the verification was installed, which is not exactly the wire write, so write
contention above the latency boundary can still inflate a fast refusal. And a
superseded write is not left alone entirely: classification is skipped, but it
still falls through to the ordinary retry path.

The supersession regression test asserted nothing: it fed a fresh ADAPTIVE report
in before its only assertion, overwriting either outcome, so it passed whether or
not the guard existed. It now asserts on the state left by the delayed echo, and
fails with the guard removed.

Also drops an unused import and restores an indent lost when the remap argument
was removed.
2026-08-20 00:06:03 +02:00
darken b396ce457d fix(aap): Attribute a listening mode echo before drawing conclusions from it
Extends the previous commit's classifier so it only judges evidence it can
actually attribute to our own write.

The classifier judged whatever mode happened to be current when the deadline
fired, and never looked at timing, despite the refusal-versus-misreport
distinction resting on it. A delayed answer to an earlier write, or a mode change
made on the pods themselves mid-request, could be taken for the answer to the
outstanding write.

Now the first report after a write is recorded with its latency, and that
recorded frame is what gets classified. A write is left alone entirely when it
was superseded by another listening mode write or by a stem press, since its
echoes can no longer be attributed. An answer arriving faster than 500ms is a
refusal, never a change: captures put refusals at 25-267ms and real changes at
815-1010ms. A re-send restamps its own send time and drops the previous attempt's
echo, so a retry is never judged on stale evidence. Latency is measured with the
monotonic clock, so a wall clock correction cannot turn a fast refusal into an
apparent change.

A misattribution is still possible, because AAP reports carry no correlation id
and a change made from iOS or another paired phone is invisible here. That is why
nothing is learned or persisted from this: the worst case is one wrong reading
that the device's next report corrects.

An earlier version of this work also carried a session-scoped remap, so that a
stem-initiated switch could be read correctly after our own write had proven the
device mislabels a value (issue #594). It is not included. The same
unattributability that bounds the classifier to a single wrong reading would have
let one misattribution rewrite every later report in the session, and a wrongly
resolved Off could persist AllowOffOption into the device profile, outliving the
session that produced it. Stem-initiated switches on an affected session are
therefore still not shown correctly.

Refs #594
2026-08-19 23:52:52 +02:00
darken 734e15c94d fix(aap): Tell a listening mode refusal apart from an unusable echo
Follow-up to the previous commit, which left the wrong half of this in place.
Distrusting the report only while the request was outstanding meant that four
seconds later the rejection cleared the pending mode, the bogus value came back,
and the user was shown an error for a mode change that had actually worked.

The two cases have different signatures, both readable from state the engine
already holds:

- A refusal echoes the mode the device is staying in, quickly. Captured Off
  refusals answer in 25-267ms with the previous mode.
- The Pro 3 misreport answers with a third mode, neither the one requested nor
  the one it was in, at normal change latency (815-1010ms).

So an echo that is neither the requested nor the previous mode is treated as an
unusable report rather than a refusal: no re-send of a write that already took
effect, no rejection, no error, and the requested mode is recorded as current.
Refusals still work, which is what the Off rejection message and the Allow Off
learning depend on.

This is deliberately engine-local. Seeding the cycle mask and Allow Off belief
from the device profile into the session would have encoded the rule directly,
but it inverts the current engine-to-profile data flow and creates a belief that
no device report can ever correct, since AirPods never report 0x1A or 0x34.
librepods keeps the same knowledge in its service layer and preferences, not in
its protocol manager.

The fault is per-session rather than per-request: across four sessions today the
pods either misreported every Adaptive write or none of them. The heuristic is
covered by unit tests but has not yet been observed handling a live bad session.
2026-08-19 20:33:32 +02:00
darken edf84248ab fix(anc): Don't show a phantom Off mode when AirPods misreport the listening mode
AirPods Pro 3 can answer a listening mode write with 0x0D 0x01 (Off) while
audibly switching to the requested mode. Seen on firmware
81.2675000075000000.6503, intermittently, and not reproducible on demand.

CAPod took that report at face value: it surfaced an Off button that isn't even
in the device's listening mode cycle, selected it, and said nothing about the
request not having been confirmed.

- visibleAncModes no longer re-admits a mode purely because it is the current
  one. That escape clause was what conjured the extra button.
- effectiveAncMode keeps showing the requested mode while our own request is
  outstanding and the device reports a mode it should not be able to reach.
- A rejected listening mode request now surfaces a message for every mode, not
  only Off. Other modes were dropped silently.
- The Allow Off inference ignores an Off report that arrived while a different
  mode was pending, so a single glitch cannot permanently persist "Off is
  allowed" into the device profile. An unsolicited Off still trains it, which
  is what keeps the option discoverable after it is enabled elsewhere.
- Verification deadline moved from a hardcoded 1000ms to 2000ms, and a matching
  device report now settles the verification when it arrives. Measured reply
  latency is 833-1008ms, so the old deadline sat inside the device's normal
  spread and could fire a bogus divergence plus a redundant re-send.

Settling verification on arrival is limited to SetAncMode deliberately. Every
other verified command is optimistically written into state when it is queued,
so its predicate is satisfied immediately and only the device's contradicting
echo makes it fail. Settling those early would swallow the rejection.

Note that AirPods never report AllowOffOption (0x34) or ListeningModeCycle
(0x1A), so which modes are permitted is always inferred, never device truth.
2026-08-19 19:09:20 +02:00
Matthias Urhahn 83dfdb9219 Merge pull request #682 from d4rken-org/feat/focus-duck-fallback
Reaction: Lower conversation volume even on phones that block it
2026-08-19 09:05:16 +02:00
darken 70b9c2981e fix(upgrade): Align the androidx.hilt declarations with the resolved 1.2.0
addWorkerManager() needed androidx.hilt 1.2.0 for its KSP compiler, which left
addDagger() asking for 1.0.0 while conflict resolution silently handed it 1.2.0,
and hilt-compiler declared twice at two different versions.

The version moves to Versions.AndroidX.Hilt.core, addDagger() stays the single
place that registers hilt-common and the hilt-compiler, and addWorkerManager()
declares only hilt-work. Declared and resolved versions now match, so the
dependency report no longer shows a 1.0.0 -> 1.2.0 substitution.

No dependency graph change: 1.2.0 is what already resolved.
2026-08-18 17:55:19 +02:00
darken 971fcbd34c fix(upgrade): Add a persistent acknowledgement safety net for Play purchases
Play auto-refunds (and revokes) purchases not acknowledged within 3 days.
The in-process ack machinery covers every case where the process lives
long enough; what it cannot cover is a process death around the Play
sheet (aggressive OEM task killers) followed by the user not reopening
the app before the deadline.

Add a gplay-only WorkManager safety net:
- PurchaseAckWorker: self-completing sweep via a new bounded
  BillingManager.ensureAllAcknowledged() that refreshes and acknowledges
  in the same coroutine (the reactive ack collector is async, so a worker
  cannot prove its acks happened through it). Retries with exponential
  backoff until the purchase's refund deadline, then gives up visibly.
- PurchaseAckScheduler: two unique work identities. A launch watch
  (REPLACE, armed and awaited before launchBillingFlow with a 30min delay
  so it cannot complete while the user is still in the sheet) and a
  discovered-purchase rescue (KEEP, 1min delay, armed directly from an
  ack pass that finds unacknowledged purchases, pre-attempt). Separate
  identities so a new purchase flow can never displace a pending rescue.
  Both triggers are fail-open: a broken WorkManager never blocks a
  purchase or an ack. WorkManager resolves via Provider at first arm so
  eager Application-time construction of the billing stack cannot
  trigger WorkManager's on-demand initialization prematurely.
- Nothing cancels the work from the foreground path: an ack pass can see
  zero unacked purchases while the sheet is still open, so the worker
  completes itself after its own reconciliation instead.

The ack pass now runs under a mutex (the worker sweep and the reactive
collector would otherwise race the token bookkeeping) and reports
per-outcome counts for the sweep result mapping.

This is a port of d4rken-org/sdmaid-se#2685; the ported sources are
byte-identical to the donor apart from the package rename.

CAPod had no explicit WorkManager wiring at all (work-runtime only
arrived transitively through Glance), so this also adds it:
- addWorkerManager() pinning androidx.work 2.7.1, the version already
  resolved via Glance, plus androidx.hilt:hilt-work and its KSP
  compiler. work-runtime-ktx is required at 2.7.1: CoroutineWorker,
  Operation.await, OneTimeWorkRequestBuilder and workDataOf all still
  live in the ktx artifact at that version. androidx.hilt moves 1.0.0 ->
  1.2.0 (by conflict resolution) because 1.0.0's hilt-compiler ships no
  KSP SymbolProcessorProvider, so @HiltWorker would generate nothing.
- WorkManagerModule providing the singleton WorkManager.
- App implements Configuration.Provider with the injected
  HiltWorkerFactory. WorkManager 2.7.1 still declares that interface as
  getWorkManagerConfiguration(), not the later property form.
- The manifest removes androidx.work's startup initializer so the
  on-demand configuration is the one that takes effect.

FOSS stays untouched behaviour-wise: all new billing types live in
src/gplay, workers need no manifest entry, and the worker factory
resolves the worker only in gplay variants.
2026-08-18 17:55:19 +02:00
Matthias Urhahn cebed0a60d Merge pull request #683 from d4rken-org/worktree-anc-selector-redesign
General: Redesign the noise control mode selector
2026-08-18 15:18:38 +02:00
darken 8817da309f ui(anc): Redesign the listening mode selector
Replaces the outlined segmented row with a filled track carrying a single
sliding thumb. The old control stacked three signals for one state (container
fill, bold, underline) in an outline treatment nothing else in the card uses,
which is what made it read as bolted on.

The track now sits at the same tonal step as the battery panel above it and
reuses the card's 16dp/12dp radii, so selection is carried by thumb position
and fill alone. Only the active mode is named and its slot expands to fit,
which keeps long translations off the width budget entirely.

Slot widths, label reveal and content tint are all derived from one clamped
fractional position, so geometry and content stay in lockstep. The thumb's
leading edge is that position in collapsed-slot units rather than a sum of
the animating widths, which would overshoot the target and crawl back.

Also fixes an invisible pending state: the in-flight branch set
activeContainerColor to secondaryContainer, which is exactly what
SegmentedButtonDefaults already used for the selected segment, so a requested
mode was indistinguishable from a confirmed one. An unconfirmed request now
draws a hollow pulsing thumb and tints the mode the pods are still in.

Transparency moves off Icons.TwoTone.Hearing, which is already the "In ear"
status chip and rendered identically two rows below it in the same card.
2026-08-18 15:04:50 +02:00
darken 27cc0e8d00 test(reaction): Cover the audio-focus duck fallback
Pins the outcome mapping of duckMusicVolume (a higher read-back stays a skip,
an unmoved one is a refusal), the focus request lifecycle (idempotent grant,
retry after denial, abandon only what is held, permanent loss clears the state
but a transient one does not), and the reaction side: focus is requested only
for an ignored write, released at teardown, re-requested by a keep-alive after
a permanent loss, and a late-landing volume write is restored.

The negative assertions are load-bearing: the MediaControl mock is relaxed, so
an accidental focus request on a healthy duck would pass silently.
2026-08-18 12:37:33 +02:00
darken cecd3088be feat(reaction): Duck via audio focus when the volume write is ignored
ColorOS 16 accepts setStreamVolume from a backgrounded app and leaves the
level where it was, so the conversation reaction ducked nothing. duckMusicVolume
now classifies the outcome (Ducked / Unchanged / Skipped) instead of collapsing
everything into a nullable duck, and a level that came back *higher* stays a
skip: that is the user raising the volume between the two reads, not a refusal.

On Unchanged the reaction requests AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK and lets
the framework attenuate the other player. Teardown abandons the focus and, if
the volume ended up below the pre-duck level anyway, restores it (guard for a
device that applies the write asynchronously). The focus request is built by an
injected factory because AudioFocusRequest.Builder is an unmocked stub in plain
JVM unit tests.
2026-08-18 12:36:30 +02:00
Matthias Urhahn 412ea369f1 Merge pull request #681 from d4rken-org/worktree-ca-duck-noop-diag
Reaction: Retry conversation volume lowering when the system ignores it
2026-08-17 18:40:57 +02:00
darken 22ca3a2a46 fix(bluetooth): Fix bogus scan-gap delays in debug logs
lastScanAt was read and written inside the log lambdas, which only run while
a logger is attached (log() checks Logging.hasReceivers first). In a release
build with recording off, the bookkeeping therefore never happened, so the
first delay of every debug recording reported the time since the *previous*
recording ended. Two logs from a support case opened with delay=878453ms and
delay=359983ms, which read as 14 and 6 minutes of suppressed scanning but
were just the gap between recordings.

The bookkeeping moves out of the lambdas, and the clock changes from
currentTimeMillis to elapsedRealtime so a wall-clock correction cannot
fabricate a gap either. That also puts the delay in the same boot-clock
domain as ScanResult.timestampNanos.
2026-08-17 13:00:02 +02:00
darken 92feea8a10 fix(reaction): Don't report a volume duck the system ignored
On ColorOS 16, setStreamVolume from a backgrounded app is accepted without
an exception and leaves the volume untouched: a debug log from an OPPO
PME110 shows "duckMusicVolume(100%): 40 -> 40 (requested 0)" while the same
call succeeded in the foreground.

duckMusicVolume returned a VolumeDuck for that, so ConversationReaction
recorded an Active session, armed the stale backstop, and later "restored" a
level that was never left. It now returns null when the read-back shows no
decrease, which routes the caller into its existing duck-no-op path: nothing
armed, nothing to restore, and a repeat START retries the duck instead of
treating the dead session as a keep-alive.

The predicate is applied >= prior rather than == prior. A route that
quantizes the target back up to the starting index attenuated nothing
either, and a volume that came back higher (user raised it between the two
reads) must not produce a duck whose restore would undo their change. The
WARN says "volume did not decrease" rather than blaming the ROM, since the
read-back alone cannot distinguish those three causes.
2026-08-17 13:00:02 +02:00
456 changed files with 8688 additions and 7085 deletions
+2
View File
@@ -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.
+16 -5
View File
@@ -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
+6 -7
View File
@@ -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
}
}
+3 -3
View File
@@ -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
+12 -1
View File
@@ -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
+1 -6
View File
@@ -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/
+1 -1
View File
@@ -1 +1 @@
5.2.3-rc0 50203000
5.2.6-rc0 50206000
+3
View File
@@ -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()
+3
View File
@@ -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
+21
View File
@@ -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
-1
View File
@@ -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.
@@ -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>
-3
View File
@@ -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>
+11 -14
View File
@@ -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="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>
-3
View File
@@ -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>
-3
View File
@@ -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>
-3
View File
@@ -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>
@@ -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>
-3
View File
@@ -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>
-3
View File
@@ -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>
-3
View File
@@ -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>
-3
View File
@@ -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>
-3
View File
@@ -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>
@@ -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>
@@ -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>
-3
View File
@@ -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>
@@ -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>
@@ -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>
-3
View File
@@ -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>
-3
View File
@@ -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>
-3
View File
@@ -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>
-3
View File
@@ -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">Jai déjà fait un don</string>
<string name="foss_upgrade_no_money_label">Jai 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>
@@ -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>
@@ -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>
-3
View File
@@ -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>
-3
View File
@@ -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>
@@ -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>
-3
View File
@@ -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>
-3
View File
@@ -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>
-3
View File
@@ -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>
-3
View File
@@ -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>
-3
View File
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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>
-3
View File
@@ -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>
@@ -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>
@@ -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>
-3
View File
@@ -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>
-3
View File
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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>
-3
View File
@@ -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>
@@ -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>
-3
View File
@@ -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>
@@ -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>
-3
View File
@@ -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>
-3
View File
@@ -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>
@@ -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>
-3
View File
@@ -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>
-3
View File
@@ -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>
-3
View File
@@ -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>
-3
View File
@@ -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>
@@ -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 test 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>
@@ -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>
-3
View File
@@ -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>
-3
View File
@@ -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>
@@ -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>
-3
View File
@@ -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>
-3
View File
@@ -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>
-3
View File
@@ -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>
@@ -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>
@@ -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>
-3
View File
@@ -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>
-3
View File
@@ -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>
-3
View File
@@ -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>
@@ -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>
-3
View File
@@ -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>
-3
View File
@@ -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>
@@ -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>
@@ -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>
@@ -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>
-3
View File
@@ -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>
-3
View File
@@ -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>
@@ -23,6 +23,7 @@ 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,
@@ -16,6 +16,7 @@ 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
@@ -25,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
@@ -33,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
@@ -174,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
@@ -256,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.
@@ -296,10 +305,13 @@ 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,
@@ -317,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
@@ -381,6 +409,7 @@ class BillingManager @Inject constructor(
}
if (outcome == AckOutcome.TRANSIENT) transientFailures++
if (outcome == AckOutcome.PERMANENT) permanentFailures++
if (abortPass) break
}
@@ -391,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
@@ -586,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
@@ -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")
}
}
@@ -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")
}
}
@@ -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")
}
}
+4 -12
View File
@@ -1,31 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="upgrades_gplay_unavailable_error">Google Play-dienste is nie beskikbaar nie.</string>
<string name="upgrades_no_purchases_found_check_account">Geen aankope gevind nie. Gebruik jy die regte rekening?</string>
<string name="upgrades_gplay_billing_error_label">Google Play-fout</string>
<string name="upgrades_gplay_billing_error_description">Daar was \'n fout in Google Play. Probeer asseblief later weer of herbegin jou foon.\n\nFout: %s</string>
<string name="upgrades_gplay_billing_result_error_label">Google Play-faktureringsfout</string>
<string name="upgrades_gplay_billing_result_error_description">Daar was \'n fout toe Google Play om jou aankoopbesonderhede gevra is. Maak die Google Play-kas skoon en herbegin jou foon.\n\nFout %s</string>
<string name="upgrades_gplay_already_owned_label">Reeds gekoop?</string>
<string name="upgrades_gplay_already_owned_description">Google Play meld dat jy reeds hierdie opgradering besit, maar dit kon nie herstel word nie. Maak seker jy gebruik die Google-rekening waarmee jy gekoop het. Play Store-sinkronisasie kan tyd neem — probeer herbegin, maak die Google Play-kas skoon, of wag eenvoudig \'n rukkie.</string>
<string name="upgrade_restore_action">Herstel aankoop</string>
<string name="upgrade_badge_label">Pro</string>
<!-- %1$s is the full localized brand ("CAPod Pro", composed from the app name + postfix) — translate the sentence around it, keep the placeholder. -->
<string name="upgrade_screen_title_template">Kry %1$s</string>
<string name="upgrade_preamble">CAPod word deur een persoon ontwikkel. Opgradering ontsluit ekstra kenmerke en help om die toepassing lewend te hou.</string>
<string name="upgrade_screen_options_description">Dieselfde kenmerke, verskillende pryse. Die intekening sluit \'n gratis proeftydperk in en kan te eniger tyd gekanselleer word.</string>
<string name="upgrade_screen_options_description_no_trial">Dieselfde kenmerke, verskillende pryse. Die intekening kan te eniger tyd gekanselleer word.</string>
<string name="upgrade_screen_subscription_trial_action">Begin gratis proeftydperk</string>
<string name="upgrade_screen_subscription_action">Teken jaarliks in</string>
<string name="upgrade_screen_subscription_action_hint">%s/jaar</string>
<string name="upgrade_screen_iap_action">Koop eenmalig</string>
<string name="upgrade_screen_iap_action_hint">Eenmalige aankoop: %s</string>
<string name="upgrade_screen_restore_purchase_action">Herstel aankoop</string>
<string name="upgrade_screen_restore_purchase_message">Geen aankope gevind nie. Gebruik jy die regte rekening?</string>
<string name="upgrade_screen_restore_troubleshooting_msg">As jy onlangs gekoop het, kan dit \'n rukkie neem vir Google Play om te sinkroniseer.</string>
<string name="upgrade_screen_restore_sync_patience_hint">Probeer weer oor \'n paar minute as jou aankoop nie verskyn nie.</string>
<string name="upgrade_screen_restore_multiaccount_hint">Maak seker jy is aangemeld met dieselfde Google-rekening wat vir die aankoop gebruik is.</string>
<string name="upgrade_screen_restore_webinstall_hint">Veelvuldige rekeninge kan Google Play verwar. Deur die toepassing via die Google Play-webwerf te installeer, word die assosiasie met \'n spesifieke rekening afgedwing.</string>
<string name="upgrade_screen_restore_banner_title">Reeds Pro gekoop?</string>
<string name="upgrade_screen_restore_banner_body">Dit lyk asof jy voorheen op hierdie toestel na Pro opgegradeer het. Herstel jou aankoop om dit weer te ontsluit.</string>
<string name="upgrade_screen_restore_success_message">Aankoop herstel.</string>
@@ -34,7 +23,6 @@
<string name="upgrade_screen_owned_hero_title">Jy het CAPod Pro!</string>
<string name="upgrade_screen_owned_hero_sub_body">Ontsluit deur jou intekening. Dankie dat jy CAPod ondersteun!</string>
<string name="upgrade_screen_owned_hero_iap_body">Ontsluit deur jou eenmalige aankoop. Dankie dat jy CAPod ondersteun!</string>
<string name="upgrade_screen_owned_sub_title">Jaarlikse intekening</string>
<string name="upgrade_screen_owned_sub_renewing_body">Jou intekening is aktief en gestel om te hernu. Fakturering word deur Google Play bestuur.</string>
<string name="upgrade_screen_owned_sub_not_renewing_body">Jou intekening is aktief, maar nie gestel om te hernu nie. Pro bly beskikbaar totdat die huidige tydperk eindig.</string>
<string name="upgrade_screen_owned_iap_title">Eenmalige aankoop</string>
@@ -46,6 +34,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Dit is \'n aparte aankoop — dit kanselleer of vergoed nie jou intekening nie. Gebruik dieselfde Google-rekening.</string>
<string name="upgrade_screen_sub_still_renewing_title">Intekening steeds aktief</string>
<string name="upgrade_screen_sub_still_renewing_message">Jou intekening is steeds gestel om te hernu. Kanselleer dit eers in Google Play, en wissel dan na die eenmalige aankoop. Pas gekanselleer? Gee Google Play \'n oomblik en probeer weer.</string>
<string name="upgrade_screen_purchase_check_failed_message">Kon nie jou aankope by Google Play nagaan nie. Probeer weer oor \'n oomblik om \'n dubbele aankoop te vermy.</string>
<string name="upgrade_screen_pending_card_title">Betaling hangende</string>
<string name="upgrade_screen_pending_card_body">Google Play verwerk steeds jou betaling. Pro word outomaties ontsluit sodra die betaling deurgaan. Sommige betaalmetodes kan \'n rukkie neem. Jy hoef verder niks te doen nie.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play het \'n aankoop met \'n hangende betaling gevind en verwerk dit steeds. Pro word outomaties ontsluit sodra die betaling deurgaan.</string>
<string name="upgrade_screen_grace_title">Jou aankoop word bevestig</string>
<string name="upgrade_screen_grace_body_short">Google Play het jou aankoop nog nie bevestig nie. Pro is steeds aktief — geen aksie nodig nie.</string>
<string name="upgrade_screen_grace_body">Google Play het jou aankoop al \'n rukkie nie bevestig nie. Pro is steeds aktief. Maak seker jy is aanlyn en aangemeld met die Google-rekening wat vir die aankoop gebruik is, en probeer dan herstel.</string>
+4 -12
View File
@@ -1,31 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="upgrades_gplay_unavailable_error">የGoogle Play አገልግሎቶች አይገኙም።</string>
<string name="upgrades_no_purchases_found_check_account">ምንም ግዢዎች አልተገኙም። ትክክለኛውን መለያ እየተጠቀሙ ነው?</string>
<string name="upgrades_gplay_billing_error_label">የGoogle Play ስህተት</string>
<string name="upgrades_gplay_billing_error_description">በGoogle Play ላይ ስህተት ነበር። እባክዎ ቆይተው ይሞክሩ ወይም ስልክዎን እንደገና ያስጀምሩ።\n\nስህተት: %s</string>
<string name="upgrades_gplay_billing_result_error_label">የGoogle Play ክፍያ ስህተት</string>
<string name="upgrades_gplay_billing_result_error_description">Google Play የግዢ ዝርዝሮችዎን ሲጠይቅ ስህተት ነበር። የGoogle Play መሸጎጫውን ያጽዱ እና ስልክዎን እንደገና ያስጀምሩ።\n\nስህተት %s</string>
<string name="upgrades_gplay_already_owned_label">አስቀድመው ገዝተዋል?</string>
<string name="upgrades_gplay_already_owned_description">Google Play ይህን ማሻሻያ አስቀድመው እንደያዙ ዘግቧል፣ ነገር ግን መመለስ አልተቻለም። በገዙበት የGoogle መለያ እየተጠቀሙ መሆኑን ያረጋግጡ። የPlay Store ማመሳሰል ጊዜ ሊወስድ ይችላል — እንደገና ለማስጀመር፣ የGoogle Play መሸጎጫን ለማጽዳት ወይም በቀላሉ ለመጠበቅ ይሞክሩ።</string>
<string name="upgrade_restore_action">ግዢን ወደነበረበት መልስ</string>
<string name="upgrade_badge_label">Pro</string>
<!-- %1$s is the full localized brand ("CAPod Pro", composed from the app name + postfix) — translate the sentence around it, keep the placeholder. -->
<string name="upgrade_screen_title_template">%1$s ያግኑ</string>
<string name="upgrade_preamble">CAPod የሚዘጋጀው በአንድ ሰው ብቻ ነው። ማሻሻል ተጨማሪ ባህሪያትን ይከፍታል እንዲሁም መተግበሪያውን ሕያው ለማድረግ ይረዳል።</string>
<string name="upgrade_screen_options_description">ተመሳሳይ ባህሪያት፣ የተለያየ ዋጋ አሰጣጥ። ምዝገባው ነጻ የሙከራ ጊዜ ያካትታል እና በማንኛውም ጊዜ ሊሰረዝ ይችላል።</string>
<string name="upgrade_screen_options_description_no_trial">ተመሳሳይ ባህሪያት፣ የተለያየ ዋጋ አሰጣጥ። ምዝገባው በማንኛውም ጊዜ ሊሰረዝ ይችላል።</string>
<string name="upgrade_screen_subscription_trial_action">ነጻ ሙከራ ጀምር</string>
<string name="upgrade_screen_subscription_action">በዓመት ይመዝገቡ</string>
<string name="upgrade_screen_subscription_action_hint">%s/ዓመት</string>
<string name="upgrade_screen_iap_action">አንዴ ይግዙ</string>
<string name="upgrade_screen_iap_action_hint">የአንድ ጊዜ ግዢ፦ %s</string>
<string name="upgrade_screen_restore_purchase_action">ግዢን ወደነበረበት መልስ</string>
<string name="upgrade_screen_restore_purchase_message">ምንም ግዢዎች አልተገኙም። ትክክለኛውን መለያ እየተጠቀሙ ነው?</string>
<string name="upgrade_screen_restore_troubleshooting_msg">በቅርቡ ገዝተው ከሆነ፣ Google Play ለማመሳሰል ትንሽ ጊዜ ሊወስድ ይችላል።</string>
<string name="upgrade_screen_restore_sync_patience_hint">ግዢዎ ካልታየ በጥቂት ደቂቃዎች ውስጥ እንደገና ይሞክሩ።</string>
<string name="upgrade_screen_restore_multiaccount_hint">ለግዢው ጥቅም ላይ በዋለው ተመሳሳይ የGoogle መለያ መግባትዎን ያረጋግጡ።</string>
<string name="upgrade_screen_restore_webinstall_hint">ብዙ መለያዎች Google Playን ማምታታት ይችላሉ። መተግበሪያውን በGoogle Play ድርጣቢያ በኩል መጫን ከተወሰነ መለያ ጋር ግንኙነትን ያስገድዳል።</string>
<string name="upgrade_screen_restore_banner_title">Pro አስቀድመው ገዝተዋል?</string>
<string name="upgrade_screen_restore_banner_body">ቀደም ሲል በዚህ መሣሪያ ወደ Pro እንዳሻሻሉ ይመስላል። እንደገና ለመክፈት ግዢዎን ይመልሱ።</string>
<string name="upgrade_screen_restore_success_message">ግዢ ተመልሷል።</string>
@@ -34,7 +23,6 @@
<string name="upgrade_screen_owned_hero_title">CAPod Pro አለዎት!</string>
<string name="upgrade_screen_owned_hero_sub_body">በምዝገባዎ ተከፍቷል። CAPod ን ስለደገፉ እናመሰግናለን!</string>
<string name="upgrade_screen_owned_hero_iap_body">በአንድ ጊዜ ግዢዎ ተከፍቷል። CAPod ን ስለደገፉ እናመሰግናለን!</string>
<string name="upgrade_screen_owned_sub_title">ዓመታዊ ምዝገባ</string>
<string name="upgrade_screen_owned_sub_renewing_body">ምዝገባዎ ንቁ ነው እና ለማደስ ተዘጋጅቶል። ክፍያ በGoogle Play ይተዳደራል።</string>
<string name="upgrade_screen_owned_sub_not_renewing_body">ምዝገባዎ ንቁ ነው፣ ነገር ግን ለማደስ አልተዘጋጀም። አሑን ያለው ጊዜ እስክያለቅ ድረስ Pro ይገኛል።</string>
<string name="upgrade_screen_owned_iap_title">አንድ ጊዜ ግዢ</string>
@@ -46,6 +34,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">ይህ የተለየ ግዢ ነው — ምዝገባውን አይሰርዝም ወይም አይመልስም። ተመሳሳይ የGoogle መለያ ይጠቀሙ።</string>
<string name="upgrade_screen_sub_still_renewing_title">ምዝገባ አሁነም ንቁ ነው</string>
<string name="upgrade_screen_sub_still_renewing_message">ምዝገባዎ አሁንም ለማደስ ተዘጋጅቷል። መጀመሪያ በGoogle Play ውስጥ ይሰርዙት፣ ከዚያ ወደ አንድ ጊዜ ግዢ ይቀይሩ። አሁን ገና ሰርዘውታል? ለGoogle Play ትንሽ ጊዜ ይስጡ እና እንደገና ይሞክሩ።</string>
<string name="upgrade_screen_purchase_check_failed_message">Google Play ውስጥ የእርስዎን ግዢ ማረጋገጥ አልተቻልም። ተደገመ ግዢ ለመቆጠብ ውሌት በኋላ ሞክሩት።</string>
<string name="upgrade_screen_pending_card_title">ክፍያ በሂደት ላይ</string>
<string name="upgrade_screen_pending_card_body">Google Play ክፍያዎን ሥራ ላይ ቀጥሏል። ክፍያው ከተጠናቀቀ በኋላ Pro ራሱ ይከፈታል። አንዳንድ ክፍያ ዘዴ ጊዜ ሊወስዱ ይችላሉ። ሌላ ምንም ሊያደርጉት የሚገቡ ነገር የለም።</string>
<string name="upgrade_screen_pending_dialog_message">Google Play ሥራ ያለበት ክፍያ ያለበት ግዢ አገኘ እና ሥራ ላይ ይገኛል። ክፍያው ከተጠናቀቀ በኋላ Pro ራሱ ይከፈታል።</string>
<string name="upgrade_screen_grace_title">ግዢዎን በማረጋገጥ ላይ</string>
<string name="upgrade_screen_grace_body_short">Google Play ግዢዎን እስካሁን አላረጋገጠም። Pro አሁንም ንቁ ነው — ምንም እርምጃ አያስፈልግም።</string>
<string name="upgrade_screen_grace_body">Google Play ለተወሰነ ጊዜ ግዢዎን አላረጋገጠም። Pro አሁንም ንቁ ነው። መስመር ላይ መሆንዎን እና ለግዢው ጥቅም ላይ በዋለው የGoogle መለያ መግባትዎን ያረጋግጡ፣ ከዚያ ወደነበረበት ለመመለስ ይሞክሩ።</string>
+48 -59
View File
@@ -1,78 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="upgrades_gplay_unavailable_error">خدمات جوجل بلاي غير متوفّرة.</string>
<string name="upgrades_no_purchases_found_check_account">لم يتمّ العثور على أيّ مشتريات. هل تستخدم الحساب الصحيح؟</string>
<string name="upgrades_gplay_billing_error_label">خطأ في جوجل بلاي</string>
<string name="upgrades_gplay_billing_error_description">حدث خطأ في سوق جوجل بلاي. يُرجى المحاولة لاحقًا أو إعادة تشغيل هاتفك.\n\nالخطأ: %s</string>
<string name="upgrades_gplay_billing_result_error_label">خطأ في فوترة جوجل بلاي</string>
<string name="upgrades_gplay_billing_result_error_description">حدث خطأ أثناء طلب تفاصيل عملية الاشتراء من سوق جوجل بلاي. يُرجى مسح ذاكرة التخزين المؤقّت لسوق جوجل بلاي وإعادة تشغيل هاتفك.\n\nالخطأ %s</string>
<string name="upgrades_gplay_already_owned_label">هل اشتريت بالفعل؟</string>
<string name="upgrades_gplay_already_owned_description">أفادت سوق جوجل بلاي بأنّك تمتلك هذا التحديث بالفعل، ولكن تعذّر استعادته. تأكّد من استخدام حساب جوجل الذي اشتريت به التطبيق. قد تستغرق مزامنة سوق بلاي بعض الوقت — حاول إعادة تشغيل الجهاز، أو مسح ذاكرة التخزين المؤقّت لسوق جوجل بلاي، أو الانتظار ببساطة.</string>
<string name="upgrade_restore_action">استعادة المشتريات</string>
<string name="upgrades_gplay_billing_error_label">خطأٌ في جوجل بلاي</string>
<string name="upgrades_gplay_billing_error_description">حدث خطأٌ في جوجل بلاي. يُرجى المحاولة لاحقًا أو إعادة تشغيل هاتفك.\n\nالخطأ: %s</string>
<string name="upgrades_gplay_already_owned_label">هل اشتريت فعلًا؟</string>
<string name="upgrades_gplay_already_owned_description">أفاد جوجل بلاي بأنّك تمتلك هذه الترقية فعلًا، ولكنْ تعذّرت استعادتها. تأكّد استخدام حساب جوجل الذي اشتريت به الترقية. قد تستغرق مزامنة سوق بلاي بعض الوقت — حاول إعادة تشغيل الجهاز، أو مسح ذاكرة التخزين المؤقّت لجوجل بلاي، أو الانتظار ببساطة.</string>
<string name="upgrade_badge_label">النسخة الاحترافية</string>
<!-- %1$s is the full localized brand ("CAPod Pro", composed from the app name + postfix) — translate the sentence around it, keep the placeholder. -->
<string name="upgrade_screen_title_template">احصل على %1$s</string>
<string name="upgrade_preamble">تمّ تطوير كابود بواسطة شخص واحد. يتيح التحديث ميزات إضافية ويساعد في استمرار عمل التطبيق.</string>
<string name="upgrade_screen_options_description">نفس الميزات، بتسعير مختلف. يشمل الاشتراك فترة تجريبية مجّانية ويمكن إلغاؤه في أيّ وقت.</string>
<string name="upgrade_screen_options_description_no_trial">نفس الميزات، بتسعير مختلف. يمكن إلغاء الاشتراك في أي وقت.</string>
<string name="upgrade_screen_subscription_trial_action">بدء الفترة التجريبية المجّانية</string>
<string name="upgrade_screen_subscription_action">الاشتراك سنويًّا</string>
<string name="upgrade_screen_subscription_action_hint">%s/سنويًّا</string>
<string name="upgrade_screen_iap_action">الاشتراء لمرّة واحدة</string>
<string name="upgrade_screen_iap_action_hint">اشتراء لمرّة واحدة: %s</string>
<string name="upgrade_screen_restore_purchase_action">استعادة المشتريات</string>
<string name="upgrade_screen_restore_purchase_message">لم يتمّ العثور على أيّ مشتريات. هل تستخدم الحساب الصحيح؟</string>
<string name="upgrade_screen_restore_troubleshooting_msg">إذا اشتريت مؤخّرًا، فقد يستغرق الأمر بعض الوقت حتى تتمّ مزامنة حسابك على جوجل بلاي.</string>
<string name="upgrade_screen_restore_sync_patience_hint">حاول مرة أخرى بعد بضع دقائق إذا لم يظهر طلبك.</string>
<string name="upgrade_screen_restore_multiaccount_hint">تأكّد من تسجيل دخولك باستخدام حساب جوجل نفسه المستخدم في عملية الاشتراء.</string>
<string name="upgrade_screen_restore_webinstall_hint">قد يؤدّي تعدّد الحسابات إلى إرباك سوق جوجل بلاي. لذا، فإنّ تثبيت التطبيق من خلال موقع جوجل بلاي الإلكتروني يجبر التطبيق على الارتباط بحساب مُحدَّد.</string>
<string name="upgrade_screen_restore_banner_title">هل اشتريت النسخة الاحترافية بالفعل؟</string>
<string name="upgrade_screen_restore_banner_body">يبدو أنّك رقّيت إلى النسخة الاحترافية على هذا الجهاز سابقًا. استعِدْ عملية الاشتراء لفتحه مرّةً أخرى.</string>
<string name="upgrade_preamble">تمّ تطوير كابود بواسطة شخصٍ واحد. تتيح الترقية مزايا إضافيةٍ وتساعد في استمرار عمل التطبيق.</string>
<string name="upgrade_screen_subscription_trial_action">ابدأ المدّة التجريبية المجّانية</string>
<string name="upgrade_screen_subscription_action">اشترك سنويًّا</string>
<string name="upgrade_screen_iap_action">اشترِ لمرّةٍ واحدة</string>
<string name="upgrade_screen_restore_purchase_action">استعِد المشتريات</string>
<string name="upgrade_screen_restore_sync_patience_hint">حاول مرّةً أخرى بعد بضع دقائق إذا لم يظهر طلبك.</string>
<string name="upgrade_screen_restore_multiaccount_hint">تأكّد تسجيل دخولك باستخدام حساب جوجل نفسه المُستخدَم في عملية الاشتراء.</string>
<string name="upgrade_screen_restore_banner_title">هل اشتريت النسخة الاحترافية فعلًا؟</string>
<string name="upgrade_screen_restore_banner_body">يبدو أنّك رقّيت إلى النسخة الاحترافية على هذا الجهاز سابقًا. استعِد عملية الاشتراء لفتحه مرّةً أخرى.</string>
<string name="upgrade_screen_restore_success_message">تمّت استعادة عملية الاشتراء.</string>
<string name="upgrade_screen_restore_status_title">هل تبدو الحالة خاطئة؟</string>
<string name="upgrade_screen_restore_status_body">إذا كنت تمتلك ترقية غير معروضة هنا، فاستعِدْ عملية الاشتراء الخاصّة بك.</string>
<string name="upgrade_screen_restore_status_body">إذا كنت تمتلك ترقيةً غير معروضةٍ هنا، فاستعِد عملية الاشتراء الخاصّة بك.</string>
<string name="upgrade_screen_owned_hero_title">لديك النسخة الاحترافية من كابود!</string>
<string name="upgrade_screen_owned_hero_sub_body">تمّ فتحها باشتراكك. شكرًا لدعمك لكابود!</string>
<string name="upgrade_screen_owned_hero_iap_body">تمّ فتحها من خلال عملية اشتراء لمرّة واحدة. شكرًا لدعمك لكابود!</string>
<string name="upgrade_screen_owned_sub_title">اشتراك سنوي</string>
<string name="upgrade_screen_owned_sub_renewing_body">اشتراكك فعّال ومُعَدّ للتجديد. تتمّ إدارة الفواتير بواسطة جوجل بلاي.</string>
<string name="upgrade_screen_owned_sub_not_renewing_body">اشتراكك فعّال، لكنّه غير مُعَدّ للتجديد. ستظلّ النسخة الاحترافية متاحةً حتّى نهاية الفترة الحالية.</string>
<string name="upgrade_screen_owned_iap_title">اشتراء لمرّة واحدة</string>
<string name="upgrade_screen_owned_hero_iap_body">تمّ فتحها باختيارك خيار الاشتراء لمرّةٍ واحدة. شكرًا لدعمك لكابود!</string>
<string name="upgrade_screen_owned_sub_renewing_body">اشتراكك فعّالٌ ومُعَدٌّ للتجديد. تتمّ إدارة الفواتير بواسطة جوجل بلاي.</string>
<string name="upgrade_screen_owned_sub_not_renewing_body">اشتراكك فعّالٌ، لكنّه غير مُعَدٍّ للتجديد. ستظلّ النسخة الاحترافية مُتاحةً حتّى نهاية المدّة الحالية.</string>
<string name="upgrade_screen_owned_iap_title">اشتراء لمرّةٍ واحدة</string>
<string name="upgrade_screen_owned_iap_body">ملكك للأبد. لا تجديدات، ولا تاريخ انتهاء.</string>
<string name="upgrade_screen_owned_both_renewing_warning">أنت تملك عملية الاشتراء لمرّة واحدة، لكنّ اشتراكك لا يزال مُعَدًّا للتجديد التلقائي. ألغِه من سوق جوجل بلاي لتجنّب الدفع مرّتين.</string>
<string name="upgrade_screen_owned_both_renewing_warning">أنت تملك عملية الاشتراء لمرّةٍ واحدة، لكنّ اشتراكك لا يزال مُعَدًّا للتجديد التلقائي. ألغِه من جوجل بلاي لتجنّب الدفع مرّتين.</string>
<string name="upgrade_screen_manage_subscription_action">إدارة الاشتراك</string>
<string name="upgrade_screen_iap_offer_title">التبديل إلى الشراء لمرة واحدة</string>
<string name="upgrade_screen_owned_iap_locked_note">يصبح متاحًا بمجرد إلغاء ضبط اشتراكك على التجديد. ألغه في Google Play للتبديل.</string>
<string name="upgrade_screen_owned_iap_purchase_note">هذا شراء منفصل — لا يؤدي إلى إلغاء اشتراكك أو استرداد قيمته. استخدم نفس حساب Google.</string>
<string name="upgrade_screen_sub_still_renewing_title">الاشتراك لا يزال نشطًا</string>
<string name="upgrade_screen_sub_still_renewing_message">اشتراكك لا يزال مضبوطًا على التجديد. ألغه في Google Play أولًا، ثم بدّل إلى الشراء لمرة واحدة. هل ألغيته للتو؟ امنح Google Play بعض الوقت وحاول مرة أخرى.</string>
<string name="upgrade_screen_grace_title">جارٍ تأكيد عملية الشراء</string>
<string name="upgrade_screen_grace_body_short">لم يؤكّد Google Play عملية الشراء الخاصة بك بعد. لا يزال Pro نشطًا — لا حاجة لاتخاذ أي إجراء.</string>
<string name="upgrade_screen_grace_body">لم يؤكّد Google Play عملية الشراء الخاصة بك منذ فترة. لا يزال Pro نشطًا. تأكد من اتصالك بالإنترنت وتسجيل دخولك بحساب Google المستخدم في عملية الشراء، ثم حاول استعادة الشراء.</string>
<!-- Offercard layout -->
<string name="upgrade_screen_iap_offer_title">التبديل إلى الاشتراء لمرّةٍ واحدة</string>
<string name="upgrade_screen_owned_iap_locked_note">يصبح هذا الخيار مُتاحًا بمجرّد إيقاف التجديد التلقائي لاشتراكك. ألغِ الاشتراك عبر جوجل بلاي لإجراء التبديل.</string>
<string name="upgrade_screen_owned_iap_purchase_note">هذا شراءٌ منفصل — لا يؤدّي إلى إلغاء اشتراكك أو استرداد قيمته. استخدم حساب جوجل نفسه.</string>
<string name="upgrade_screen_sub_still_renewing_title">الاشتراك لا يزال فعّالًا</string>
<string name="upgrade_screen_sub_still_renewing_message">لا يزال اشتراكك مُعَدًّا للتجديد التلقائي. ألغِه من جوجل بلاي أوّلًا، ثمّ بدّل إلى خيار الاشتراء لمرّةٍ واحدة. هل ألغيت للتوّ؟ انتظر قليلًا ثمّ حاول مرّةً أخرى من جوجل بلاي.</string>
<string name="upgrade_screen_purchase_check_failed_message">تعذّرت مراجعة مشترياتك باستخدام جوجل بلاي. لتجنّب عملية اشتراءٍ مُكرَّرة، حاول مرّةً أخرى بعد قليل.</string>
<string name="upgrade_screen_pending_card_title">الدُّفعة قيد الانتظار</string>
<string name="upgrade_screen_pending_card_body">لا يزال جوجل بلاي يعالج دُفعتك. سيتمّ تفعيل النسخة الاحترافية تلقائيًّا بمجرّد إتمام الدفع. قد تستغرق بعض طرق الدفع بعض الوقت. لا يلزمك فعل أيّ شيءٍ آخر.</string>
<string name="upgrade_screen_pending_dialog_message">عثر جوجل بلاي على عملية اشتراء ذي دُفعة مُعلَّقة، ولا يزال يعمل على معالجتها. سيتمّ تفعيل النسخة الاحترافية تلقائيًّا بمجرّد إتمام عملية الدفع.</string>
<string name="upgrade_screen_grace_title">يجري تأكيد عملية الاشتراء</string>
<string name="upgrade_screen_grace_body_short">لم يؤكّد جوجل بلاي عملية الاشتراء بعد. لا تزال النسخة الاحترافية فعّالةً، فلا داعي لاتّخاذ أيّ إجراء.</string>
<string name="upgrade_screen_grace_body">لم يؤكّد جوجل بلاي عملية الاشتراء منذ مدّة. لا تزال النسخة الاحترافية فعّالةً. راجع اتّصالك بالإنترنت وتسجيل دخولك باستخدام حساب جوجل المُستخدَم في عملية الاشتراء، ثمّ حاول استعادة حسابك.</string>
<string name="upgrade_screen_offers_title">خيارات الترقية</string>
<string name="upgrade_screen_offers_or">أو</string>
<string name="upgrade_screen_offers_body">نفس الميزات، ولكن نماذج تسعير مختلفة.</string>
<string name="upgrade_screen_offers_body">نفس المزايا، ولكنّ نماذج تسعيرٍ مختلفة.</string>
<string name="upgrade_screen_offers_unavailable_title">الأسعار غير متوفّرة</string>
<string name="upgrade_screen_offers_unavailable_message">لم تعرض سوق جوجل بلاي الأسعار حاليًّا. تحقّق من اتّصالك بالإنترنت وحاول مرّة أخرى بعد قليل.</string>
<string name="upgrade_screen_offers_unavailable_message">لم يعرض جوجل بلاي الأسعار حاليًّا. راجع اتّصالك بالإنترنت وحاول مرّةً أخرى بعد قليل.</string>
<string name="upgrade_screen_subscription_offer_title">الاشتراك السنوي</string>
<string name="upgrade_screen_subscription_offer_body">يتمّ التجديد سنويًّا بعد انتهاء الفترة التجريبية المجّانية. يمكنك الإلغاء في أيّ وقت من خلال سوق جوجل بلاي.</string>
<string name="upgrade_screen_subscription_offer_body_no_trial">يتم التجديد سنويًّا. يمكنك الإلغاء في أيّ وقت من خلال سوق جوجل بلاي.</string>
<string name="upgrade_screen_iap_offer_body">دفعة واحدة، بدون تجديدات.</string>
<string name="upgrade_screen_subscription_offer_body">يتمّ التجديد سنويًّا بعد انتهاء المدّة التجريبية المجّانية. يمكنك الإلغاء في أيّ وقتٍ عبر جوجل بلاي.</string>
<string name="upgrade_screen_subscription_offer_body_no_trial">يتمّ التجديد سنويًّا. يمكنك الإلغاء في أيّ وقتٍ عبر جوجل بلاي.</string>
<string name="upgrade_screen_iap_offer_body">دُفعةً واحدةً، دون تجديدات.</string>
<string name="upgrade_screen_benefits_title">مزايا الترقية</string>
<string name="upgrade_screen_restore_body">تطلب عملية الاستعادة من سوق جوجل بلاي إعادة التحقّق من مشتريات هذا التطبيق للحساب الحالي. ولا تبدأ عملية اشتراء جديدة.</string>
<string name="upgrade_screen_restore_checked_message">تحقَّق كابود من جوجل بلاي، ولكن لم يتمّ تأكيد أيّ عملية اشتراء للنسخة الاحترافية للحساب الذي يستخدمه جوجل بلاي لهذا التطبيق.</string>
<string name="upgrade_screen_restore_contact_hint">هل ما زلت تواجه مشكلة؟ تواصل مع الدعم من خلال البريد الإلكتروني الذي استخدمته لإتمام عملية الشراء، وضمّن رقم طلبك من سوق جوجل بلاي.</string>
<string name="upgrade_screen_contact_support_action">التواصُل مع الدعم</string>
<string name="upgrade_screen_restore_body">تطلب عملية الاستعادة من جوجل بلاي إعادة مراجعة مشتريات هذا التطبيق للحساب الحالي. ولا تبدأ عملية اشتراءٍ جديدة.</string>
<string name="upgrade_screen_restore_checked_message">راجَع كابود جوجل بلاي، ولكنْ لم يتمّ تأكيد أيّ عملية اشتراء النسخة الاحترافية للحساب الذي يستخدمه جوجل بلاي لهذا التطبيق.</string>
<string name="upgrade_screen_restore_contact_hint">هل ما زلت تواجه مشكلةً؟ تواصَل مع الدعم عبر البريد الإلكتروني الذي استخدمته لإتمام عملية الاشتراء، وضمّن رقم طلبك من جوجل بلاي.</string>
<string name="upgrade_screen_contact_support_action">تواصَل مع الدعم</string>
<string name="settings_upgrade_status_description">حالة الترقية والاشتراء الخاصّة بك.</string>
<string name="upgrades_gplay_unavailable_error_description">لا يمكن لكابود الاتّصال بسوق جوجل بلاي. هل سوق جوجل بلاي مُثبَّتة ومُحدَّثة؟ هل حساب جوجل الخاصّ بك مُسجَّل الدخول؟ حاول مسح ذاكرة التخزين المؤقّت لتطبيق جوجل بلاي وإعادة تشغيل جهازك.</string>
<!-- Toast shown when the "Google Play" button of an error dialog can't open Google Play's app info, e.g. Google Play is missing or blocked on this device. "Google Play" is a brand name, keep it untranslated. -->
<string name="upgrades_gplay_not_installed_message">تعذر فتح Google Play. قد يكون التطبيق غير موجود أو معطل أو مقيد على هذا الجهاز.</string>
<string name="upgrades_gplay_internal_error_title">خطأ في جوجل بلاي</string>
<string name="upgrades_gplay_internal_error_description">حدث خطأ داخلي في سوق جوجل بلاي. يُرجى تجربة ما يلي:\n\n• أعِد تشغيل جهازك\n• امسح ذاكرة التخزين المؤقّت لسوق جوجل بلاي\n• حاول مرّة أخرى لاحقًا</string>
<string name="upgrades_gplay_network_error_title">خطأ في الاتّصال</string>
<string name="upgrades_gplay_network_error_description">تعذّر الاتصال بسوق جوجل بلاي. يُرجى التحقّق من اتّصالك بالإنترنت والمحاولة مرّة أخرى.</string>
<string name="upgrades_gplay_unavailable_error_description">لا يمكن لكابود الاتّصال بجوجل بلاي. هل جوجل بلاي مُثبَّتٌ ومُحدَّث؟ هل حساب جوجل الخاصّ بك مُسجَّل الدخول؟ حاول مسح ذاكرة التخزين المؤقّت لتطبيق جوجل بلاي وإعادة تشغيل جهازك.</string>
<string name="upgrades_gplay_not_installed_message">تعذّر فتح جوجل بلاي. قد يكون غير موجودٍ أو مُعطَّلٍ أو مُقيَّدٍ على هذا الجهاز.</string>
<string name="upgrades_gplay_internal_error_title">خطأٌ في جوجل بلاي</string>
<string name="upgrades_gplay_internal_error_description">حدث خطأٌ داخليٌّ في جوجل بلاي. يُرجى تجربة ما يلي:\n\n• أعِد تشغيل جهازك\n• امسح ذاكرة التخزين المؤقّت لجوجل بلاي\n• حاول مرّةً أخرى لاحقًا</string>
<string name="upgrades_gplay_network_error_title">خطأٌ في الاتّصال</string>
<string name="upgrades_gplay_network_error_description">تعذّر الاتصال بجوجل بلاي. يُرجى التحقّق من اتّصالك بالإنترنت والمحاولة مرّةً أخرى.</string>
<string name="upgrades_gplay_offer_unavailable_title">العرض غير متوفّر</string>
<string name="upgrades_gplay_offer_unavailable_description">لا تقدّم سوق جوجل بلاي حاليًّا خيار الترقية هذا. يُرجى المحاولة لاحقًا أو مراجعة سوق جوجل بلاي.</string>
<string name="upgrade_screen_restore_inconclusive_message">لم يكتمل التحقّق من سوق جوجل بلاي في الوقت المُحدَّد، لذا لا تزال حالة عملية الاشتراء غير معروفة. لم يُطرأ أيّ تغيير على حسابك.</string>
<string name="upgrades_gplay_offer_unavailable_description">لا يقدّم جوجل بلاي حاليًّا خيار الترقية هذا. يُرجى المحاولة لاحقًا أو مراجعة سوق جوجل بلاي.</string>
<string name="upgrade_screen_restore_inconclusive_message">لم تكتمل عملية التحقّق مع جوجل بلاي في الوقت المناسب، ولذلك لا تزال حالة عملية الاشتراء غير معروفة. لم يطرأ أيّ تغييرٍ على حسابك.</string>
</resources>
+4 -12
View File
@@ -1,31 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="upgrades_gplay_unavailable_error">Google Play xidmətləri əlçatmazdır.</string>
<string name="upgrades_no_purchases_found_check_account">Heç bir satın alma tapılmadı. Doğru hesabı istifadə edirsiniz?</string>
<string name="upgrades_gplay_billing_error_label">Google Play xətası</string>
<string name="upgrades_gplay_billing_error_description">Google Play-də xəta baş verdi. Zəhmət olmasa daha sonra yenidən cəhd edin və ya telefonunuzu yenidən başladın.\n\nXəta: %s</string>
<string name="upgrades_gplay_billing_result_error_label">Google Play faktura xətası</string>
<string name="upgrades_gplay_billing_result_error_description">Google Play-dən satınalma təfərrüatlarınızı soruşarkən xəta baş verdi. Google Play keşini təmizləyin və telefonunuzu yenidən başladın.\n\nXəta %s</string>
<string name="upgrades_gplay_already_owned_label">Artıq satın almısınız?</string>
<string name="upgrades_gplay_already_owned_description">Google Play bu yüksəltməyə artıq sahib olduğunuzu bildirir, lakin bərpa edilə bilmədi. Satın aldığınız Google hesabından istifadə etdiyinizə əmin olun. Play Store sinxronizasiyası bir qədər vaxt apara bilər — yenidən başlatmağı, Google Play keşini təmizləməyi və ya sadəcə gözləməyi sınayın.</string>
<string name="upgrade_restore_action">Alışı bərpa et</string>
<string name="upgrade_badge_label">Pro</string>
<!-- %1$s is the full localized brand ("CAPod Pro", composed from the app name + postfix) — translate the sentence around it, keep the placeholder. -->
<string name="upgrade_screen_title_template">%1$s əldə edin</string>
<string name="upgrade_preamble">CAPod tək bir şəxs tərəfindən hazırlanır. Yüksəltmə əlavə funksiyaların kilidini açır və tətbiqin yaşamasına kömək edir.</string>
<string name="upgrade_screen_options_description">Eyni funksiyalar, fərqli qiymətləndirmə. Abunəliyə pulsuz sınaq müddəti daxildir və istənilən vaxt ləğv edilə bilər.</string>
<string name="upgrade_screen_options_description_no_trial">Eyni funksiyalar, fərqli qiymətləndirmə. Abunəlik istənilən vaxt ləğv edilə bilər.</string>
<string name="upgrade_screen_subscription_trial_action">Pulsuz sınağı başlat</string>
<string name="upgrade_screen_subscription_action">İllik abunə ol</string>
<string name="upgrade_screen_subscription_action_hint">%s/il</string>
<string name="upgrade_screen_iap_action">Bir dəfə satın al</string>
<string name="upgrade_screen_iap_action_hint">Birdəfəlik satınalma: %s</string>
<string name="upgrade_screen_restore_purchase_action">Alışı bərpa et</string>
<string name="upgrade_screen_restore_purchase_message">Heç bir satın alma tapılmadı. Doğru hesabı istifadə edirsiniz?</string>
<string name="upgrade_screen_restore_troubleshooting_msg">Yaxınlarda satın almısınızsa, Google Play-in sinxronlaşması bir qədər vaxt apara bilər.</string>
<string name="upgrade_screen_restore_sync_patience_hint">Satınalmanız görünmürsə, bir neçə dəqiqədən sonra yenidən cəhd edin.</string>
<string name="upgrade_screen_restore_multiaccount_hint">Satınalma üçün istifadə edilən eyni Google hesabı ilə daxil olduğunuzdan əmin olun.</string>
<string name="upgrade_screen_restore_webinstall_hint">Birdən çox hesab Google Play-i çaşdıra bilər. Tətbiqi Google Play saytı vasitəsilə quraşdırmaq müəyyən bir hesabla əlaqələndirməyi məcburi edir.</string>
<string name="upgrade_screen_restore_banner_title">Artıq Pro almısınız?</string>
<string name="upgrade_screen_restore_banner_body">Görünür ki, bu cihazda əvvəllər Pro-ya yüksəltmisiniz. Onu yenidən kiliddan çıxarmaq üçün satınalmanızı bərpa edin.</string>
<string name="upgrade_screen_restore_success_message">Satınalma bərpa edildi.</string>
@@ -34,7 +23,6 @@
<string name="upgrade_screen_owned_hero_title">Sizdə CAPod Pro var!</string>
<string name="upgrade_screen_owned_hero_sub_body">Abunəliyiniz vasitəsilə kiliddan çıxarıldı. CAPod-u dəstəklədiyiniz üçün təşəkkürlər!</string>
<string name="upgrade_screen_owned_hero_iap_body">Birdəfəlik satınalmanız vasitəsilə kiliddan çıxarıldı. CAPod-u dəstəklədiyiniz üçün təşəkkürlər!</string>
<string name="upgrade_screen_owned_sub_title">İllik abunə</string>
<string name="upgrade_screen_owned_sub_renewing_body">Abunəliyiniz aktivdir və yenilənməyə təyin edilib. Ödəniş Google Play tərəfindən idarə olunur.</string>
<string name="upgrade_screen_owned_sub_not_renewing_body">Abunəliyiniz aktivdir, lakin yenilənməyə təyin edilməyib. Pro cari dövr bitənə qədər əlçatan qalır.</string>
<string name="upgrade_screen_owned_iap_title">Birdəfəlik alış</string>
@@ -46,6 +34,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Bu ayrı bir satınalmadır — abunəliyinizi ləğv etmir və ya geri ödəmir. Eyni Google hesabından istifadə edin.</string>
<string name="upgrade_screen_sub_still_renewing_title">Abunəlik hələ də aktivdir</string>
<string name="upgrade_screen_sub_still_renewing_message">Abunəliyiniz hələ də yenilənməyə təyin edilib. Əvvəlcə onu Google Play-də ləğv edin, sonra birdəfəlik satınalmaya keçin. Bu yaxınlarda ləğv etmisiniz? Google Play-ə bir az vaxt verin və yenidən cəhd edin.</string>
<string name="upgrade_screen_purchase_check_failed_message">Satın almalarını Google Play-də yoxlamaq olmadı. İki dəfə satın almamaq üçün bir az sonra yenidən cəhd et.</string>
<string name="upgrade_screen_pending_card_title">Ödəniş gözləyir</string>
<string name="upgrade_screen_pending_card_body">Google Play hələ də ödənişini emal etir. Ödəniş dəvam etdikdən sonra Pro avtomatik olaraq açılacaq. Bəzi ödəniş üsulları bir müddət çəkə bilər. Başqa heç bir şey etmən lazım deyil.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play, gözləmə ödənişi olan bir satın almağı tapdı və hələ də onu emal etir. Ödəniş dəvam etdikdən sonra Pro avtomatik olaraq açılacaq.</string>
<string name="upgrade_screen_grace_title">Satınalmanız təsdiqlənir</string>
<string name="upgrade_screen_grace_body_short">Google Play hələ satınalmanızı təsdiqləməyib. Pro hələ də aktivdir — heç bir əməliyyat lazım deyil.</string>
<string name="upgrade_screen_grace_body">Google Play bir müddətdir satınalmanızı təsdiqləməyib. Pro hələ də aktivdir. Onlayn olduğunuzdan və satınalma üçün istifadə edilən Google hesabı ilə daxil olduğunuzdan əmin olun, sonra bərpa etməyi sınayın.</string>
+1 -12
View File
@@ -1,31 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="upgrades_gplay_unavailable_error">Службы Google Play недаступныя.</string>
<string name="upgrades_no_purchases_found_check_account">Пакупак не знойдзена. Вы выкарыстоўваеце правільны ўліковы запіс?</string>
<string name="upgrades_gplay_billing_error_label">Памылка Google Play</string>
<string name="upgrades_gplay_billing_error_description">Адбылася памылка ў Google Play. Калі ласка, паспрабуйце пазней або перазагрузіце тэлефон.\n\nПамылка: %s</string>
<string name="upgrades_gplay_billing_result_error_label">Памылка выстаўлення рахункаў Google Play</string>
<string name="upgrades_gplay_billing_result_error_description">Адбылася памылка пры запыце дэталяў пакупкі ў Google Play. Ачысціце кэш Google Play і перазагрузіце тэлефон.\n\nПамылка %s</string>
<string name="upgrades_gplay_already_owned_label">Ужо купілі?</string>
<string name="upgrades_gplay_already_owned_description">Google Play паведамляе, што вы ўжо валодаеце гэтым абнаўленнем, але яго не ўдалося аднавіць. Пераканайцеся, што вы выкарыстоўваеце той акаунт Google, з якім рабілі пакупку. Сінхранізацыя Play Store можа заняць час — паспрабуйце перазагрузіць прыладу, ачысціць кэш Google Play або проста пачакаць.</string>
<string name="upgrade_restore_action">Аднавіць пакупку</string>
<string name="upgrade_badge_label">Pro</string>
<!-- %1$s is the full localized brand ("CAPod Pro", composed from the app name + postfix) — translate the sentence around it, keep the placeholder. -->
<string name="upgrade_screen_title_template">Атрымаць %1$s</string>
<string name="upgrade_preamble">CAPod распрацоўвае адзін чалавек. Абнаўленне да Pro адкрывае дадатковыя функцыі і дапамагае падтрымліваць праграму жывой.</string>
<string name="upgrade_screen_options_description">Тыя ж функцыі, розныя цэны. Падпіска ўключае бясплатны пробны перыяд і можа быць скасавана ў любы момант.</string>
<string name="upgrade_screen_options_description_no_trial">Тыя ж функцыі, розныя цэны. Падпіску можна скасаваць у любы момант.</string>
<string name="upgrade_screen_subscription_trial_action">Пачаць бясплатны пробны перыяд</string>
<string name="upgrade_screen_subscription_action">Падпісацца на год</string>
<string name="upgrade_screen_subscription_action_hint">%s/год</string>
<string name="upgrade_screen_iap_action">Купіць адзін раз</string>
<string name="upgrade_screen_iap_action_hint">Аднаразовая пакупка: %s</string>
<string name="upgrade_screen_restore_purchase_action">Аднавіць пакупку</string>
<string name="upgrade_screen_restore_purchase_message">Пакупак не знойдзена. Вы выкарыстоўваеце правільны ўліковы запіс?</string>
<string name="upgrade_screen_restore_troubleshooting_msg">Калі вы нядаўна зрабілі пакупку, Google Play можа спатрэбіцца час на сінхранізацыю.</string>
<string name="upgrade_screen_restore_sync_patience_hint">Паспрабуйце яшчэ раз праз некалькі хвілін, калі ваша пакупка не з\'явілася.</string>
<string name="upgrade_screen_restore_multiaccount_hint">Пераканайцеся, што вы ўвайшлі ў той жа акаунт Google, які выкарыстоўваўся для пакупкі.</string>
<string name="upgrade_screen_restore_webinstall_hint">Некалькі акаунтаў могуць заблытаць Google Play. Усталяванне праграмы праз сайт Google Play прымусова прывязвае яе да канкрэтнага акаунта.</string>
<string name="upgrade_screen_restore_banner_title">Ужо купілі Pro?</string>
<string name="upgrade_screen_restore_banner_body">Падобна, што вы раней абнаўляліся да Pro на гэтай прыладзе. Аднавіце пакупку, каб зноў яе разблакаваць.</string>
<string name="upgrade_screen_restore_success_message">Пакупка адноўлена.</string>
@@ -34,7 +23,6 @@
<string name="upgrade_screen_owned_hero_title">У вас ёсць CAPod Pro!</string>
<string name="upgrade_screen_owned_hero_sub_body">Разблакавана вашай падпіскай. Дзякуй за падтрымку CAPod!</string>
<string name="upgrade_screen_owned_hero_iap_body">Разблакавана вашай аднаразовай пакупкай. Дзякуй за падтрымку CAPod!</string>
<string name="upgrade_screen_owned_sub_title">Гадавая падпіска</string>
<string name="upgrade_screen_owned_sub_renewing_body">Ваша падпіска актыўная і будзе аўтаматычна прадоўжана. Аплатай кіруе Google Play.</string>
<string name="upgrade_screen_owned_sub_not_renewing_body">Ваша падпіска актыўная, але не будзе аўтаматычна прадоўжана. Pro застанецца даступным да канца бягучага перыяду.</string>
<string name="upgrade_screen_owned_iap_title">Аднаразовая пакупка</string>
@@ -46,6 +34,7 @@
<string name="upgrade_screen_owned_iap_purchase_note">Гэта асобная пакупка — яна не скасоўвае падпіску і не вяртае за яе грошы. Выкарыстоўвайце той жа акаунт Google.</string>
<string name="upgrade_screen_sub_still_renewing_title">Падпіска ўсё яшчэ актыўная</string>
<string name="upgrade_screen_sub_still_renewing_message">Ваша падпіска ўсё яшчэ будзе аўтаматычна прадоўжана. Спачатку скасуйце яе ў Google Play, а потым пераключацеся на аднаразовую пакупку. Толькі што скасавалі? Дайце Google Play крыху часу і паспрабуйце зноў.</string>
<string name="upgrade_screen_pending_card_title">Плата разглядаецца</string>
<string name="upgrade_screen_grace_title">Пацвярджэнне вашай пакупкі</string>
<string name="upgrade_screen_grace_body_short">Google Play яшчэ не пацвердзіў вашу пакупку. Pro па-ранейшаму актыўны — дзеянні не патрабуюцца.</string>
<string name="upgrade_screen_grace_body">Google Play ужо некаторы час не пацвярджае вашу пакупку. Pro па-ранейшаму актыўны. Пераканайцеся, што вы падключаны да інтэрнэту і ўвайшлі ў акаунт Google, які выкарыстоўваўся для пакупкі, а потым паспрабуйце аднавіць пакупку.</string>
+4 -12
View File
@@ -1,31 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="upgrades_gplay_unavailable_error">Услугите на Google Play не са налични.</string>
<string name="upgrades_no_purchases_found_check_account">Няма намерени покупки. Правилния акаунт ли използвате?</string>
<string name="upgrades_gplay_billing_error_label">Грешка в Google Play</string>
<string name="upgrades_gplay_billing_error_description">Възникна грешка в Google Play. Моля, опитайте отново по-късно или рестартирайте телефона си.\n\nГрешка: %s</string>
<string name="upgrades_gplay_billing_result_error_label">Грешка при фактуриране в Google Play</string>
<string name="upgrades_gplay_billing_result_error_description">Възникна грешка при заявката за данни за покупката ви от Google Play. Изчистете кеша на Google Play и рестартирайте телефона си.\n\nГрешка %s</string>
<string name="upgrades_gplay_already_owned_label">Вече сте закупили?</string>
<string name="upgrades_gplay_already_owned_description">Google Play съобщава, че вече притежавате тази надстройка, но тя не можа да бъде възстановена. Уверете се, че използвате акаунта в Google, с който сте направили покупката. Синхронизирането с Play Store може да отнеме време — опитайте да рестартирате устройството, да изчистите кеша на Google Play или просто изчакайте.</string>
<string name="upgrade_restore_action">Възстановете покупката</string>
<string name="upgrade_badge_label">Pro</string>
<!-- %1$s is the full localized brand ("CAPod Pro", composed from the app name + postfix) — translate the sentence around it, keep the placeholder. -->
<string name="upgrade_screen_title_template">Получете %1$s</string>
<string name="upgrade_preamble">CAPod се разработва от един-единствен човек. Надстройката отключва допълнителни функции и помага приложението да продължи да съществува.</string>
<string name="upgrade_screen_options_description">Едни и същи функции, различно ценообразуване. Абонаментът включва безплатен пробен период и може да бъде отказан по всяко време.</string>
<string name="upgrade_screen_options_description_no_trial">Едни и същи функции, различно ценообразуване. Абонаментът може да бъде отказан по всяко време.</string>
<string name="upgrade_screen_subscription_trial_action">Стартирай безплатен пробен период</string>
<string name="upgrade_screen_subscription_action">Абонирай се годишно</string>
<string name="upgrade_screen_subscription_action_hint">%s/година</string>
<string name="upgrade_screen_iap_action">Купи еднократно</string>
<string name="upgrade_screen_iap_action_hint">Еднократна покупка: %s</string>
<string name="upgrade_screen_restore_purchase_action">Възстановете покупката</string>
<string name="upgrade_screen_restore_purchase_message">Няма намерени покупки. Правилния акаунт ли използвате?</string>
<string name="upgrade_screen_restore_troubleshooting_msg">Ако наскоро сте направили покупка, може да отнеме известно време на Google Play да се синхронизира.</string>
<string name="upgrade_screen_restore_sync_patience_hint">Опитайте отново след няколко минути, ако покупката ви не се появи.</string>
<string name="upgrade_screen_restore_multiaccount_hint">Уверете се, че сте влезли със същия акаунт в Google, използван за покупката.</string>
<string name="upgrade_screen_restore_webinstall_hint">Няколко акаунта могат да объркат Google Play. Инсталирането на приложението през уебсайта на Google Play налага асоциирането с конкретен акаунт.</string>
<string name="upgrade_screen_restore_banner_title">Вече сте закупили Pro?</string>
<string name="upgrade_screen_restore_banner_body">Изглежда, че преди сте надстроили до Pro на това устройство. Възстановете покупката си, за да го отключите отново.</string>
<string name="upgrade_screen_restore_success_message">Покупката е възстановена.</string>
@@ -34,7 +23,6 @@
<string name="upgrade_screen_owned_hero_title">Имате CAPod Pro!</string>
<string name="upgrade_screen_owned_hero_sub_body">Отключено благодарение на вашия абонамент. Благодарим ви, че подкрепяте CAPod!</string>
<string name="upgrade_screen_owned_hero_iap_body">Отключено благодарение на еднократната ви покупка. Благодарим ви, че подкрепяте CAPod!</string>
<string name="upgrade_screen_owned_sub_title">Годишен абонамент</string>
<string name="upgrade_screen_owned_sub_renewing_body">Абонаментът ви е активен и е настроен да се подновява. Таксуването се управлява от Google Play.</string>
<string name="upgrade_screen_owned_sub_not_renewing_body">Абонаментът ви е активен, но не е настроен да се подновява. Pro остава достъпен до края на текущия период.</string>
<string name="upgrade_screen_owned_iap_title">Еднократна покупка</string>
@@ -46,6 +34,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Това е отделна покупка — тя не отменя и не възстановява средства по абонамента ви. Използвайте същия акаунт в Google.</string>
<string name="upgrade_screen_sub_still_renewing_title">Абонаментът все още е активен</string>
<string name="upgrade_screen_sub_still_renewing_message">Абонаментът ви все още е настроен да се подновява. Първо го отменете в Google Play, а след това преминете към еднократната покупка. Току-що отменихте? Изчакайте малко Google Play и опитайте отново.</string>
<string name="upgrade_screen_purchase_check_failed_message">Не можахме да проверим покупките ти в Google Play. За да избегнеш двойна покупка, опитай отново след малко.</string>
<string name="upgrade_screen_pending_card_title">Плащане в изчакване</string>
<string name="upgrade_screen_pending_card_body">Google Play все още обработва плащането ти. Pro се отключва автоматично, щом плащането бъде извършено. При някои методи на плащане това може да отнеме известно време. Не е нужно да правиш нищо друго.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play откри покупка с изчакващо плащане и все още я обработва. Pro се отключва автоматично, щом плащането бъде извършено.</string>
<string name="upgrade_screen_grace_title">Потвърждаване на покупката ви</string>
<string name="upgrade_screen_grace_body_short">Google Play все още не е потвърдил покупката ви. Pro все още е активен — не са необходими действия.</string>
<string name="upgrade_screen_grace_body">Google Play не е потвърдил покупката ви от известно време. Pro все още е активен. Уверете се, че сте онлайн и влезли с акаунта в Google, използван за покупката, след което опитайте да възстановите.</string>
+4 -12
View File
@@ -1,31 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="upgrades_gplay_unavailable_error">Google Play পরিষেবাগুলি উপলব্ধ নেই৷</string>
<string name="upgrades_no_purchases_found_check_account">কোনও কেনাকাটা খুঁজে পাওয়া যায়নি। আপনি কি সঠিক অ্যাকাউন্ট ব্যবহার করছেন?</string>
<string name="upgrades_gplay_billing_error_label">Google Play ত্রুটি</string>
<string name="upgrades_gplay_billing_error_description">Google Play-তে একটি ত্রুটি হয়েছে। অনুগ্রহ করে পরে আবার চেষ্টা করুন অথবা আপনার ফোন রিবুট করুন।\n\nত্রুটি: %s</string>
<string name="upgrades_gplay_billing_result_error_label">Google Play বিলিং ত্রুটি</string>
<string name="upgrades_gplay_billing_result_error_description">Google Play-তে আপনার কেনাকাটার বিবরণ জানতে চাইলে একটি ত্রুটি হয়েছে। Google Play ক্যাশে সাফ করুন এবং আপনার ফোন রিবুট করুন।\n\nত্রুটি %s</string>
<string name="upgrades_gplay_already_owned_label">ইতিমধ্যে কেনা হয়ে গেছে?</string>
<string name="upgrades_gplay_already_owned_description">Google Play রিপোর্ট করছে যে আপনি ইতিমধ্যে এই আপগ্রেডের মালিক, কিন্তু এটি পুনরুদ্ধার করা যায়নি। আপনি যে Google অ্যাকাউন্ট দিয়ে কিনেছিলেন সেটিই ব্যবহার করছেন কিনা তা নিশ্চিত করুন। Play Store সিঙ্ক্রোনাইজেশনে সময় লাগতে পারে — রিবুট করা, Google Play ক্যাশে সাফ করা বা কিছুক্ষণ অপেক্ষা করার চেষ্টা করুন।</string>
<string name="upgrade_restore_action">ক্রয় পুনরুদ্ধার</string>
<string name="upgrade_badge_label">Pro</string>
<!-- %1$s is the full localized brand ("CAPod Pro", composed from the app name + postfix) — translate the sentence around it, keep the placeholder. -->
<string name="upgrade_screen_title_template">%1$s পান</string>
<string name="upgrade_preamble">CAPod একজন মাত্র ব্যক্তি দ্বারা তৈরি। আপগ্রেড করলে অতিরিক্ত ফিচার আনলক হয় এবং অ্যাপটি সচল রাখতে সাহায্য করে।</string>
<string name="upgrade_screen_options_description">একই ফিচার, ভিন্ন মূল্য। সাবস্ক্রিপশনে একটি বিনামূল্যে ট্রায়াল রয়েছে এবং যেকোনো সময় বাতিল করা যায়।</string>
<string name="upgrade_screen_options_description_no_trial">একই ফিচার, ভিন্ন মূল্য। সাবস্ক্রিপশন যেকোনো সময় বাতিল করা যায়।</string>
<string name="upgrade_screen_subscription_trial_action">বিনামূল্যের ট্রায়াল শুরু করুন</string>
<string name="upgrade_screen_subscription_action">বার্ষিক সাবস্ক্রাইব করুন</string>
<string name="upgrade_screen_subscription_action_hint">%s/বছর</string>
<string name="upgrade_screen_iap_action">একবার ক্রয় করুন</string>
<string name="upgrade_screen_iap_action_hint">এককালীন ক্রয়: %s</string>
<string name="upgrade_screen_restore_purchase_action">ক্রয় পুনরুদ্ধার</string>
<string name="upgrade_screen_restore_purchase_message">কোনও কেনাকাটা খুঁজে পাওয়া যায়নি। আপনি কি সঠিক অ্যাকাউন্ট ব্যবহার করছেন?</string>
<string name="upgrade_screen_restore_troubleshooting_msg">আপনি যদি সম্প্রতি কিনে থাকেন, তাহলে Google Play সিঙ্ক করতে কিছুটা সময় নিতে পারে।</string>
<string name="upgrade_screen_restore_sync_patience_hint">আপনার কেনাকাটা দেখা না গেলে কয়েক মিনিট পর আবার চেষ্টা করুন।</string>
<string name="upgrade_screen_restore_multiaccount_hint">কেনাকাটার জন্য ব্যবহৃত একই Google অ্যাকাউন্ট দিয়ে সাইন ইন করা আছে কিনা তা নিশ্চিত করুন।</string>
<string name="upgrade_screen_restore_webinstall_hint">একাধিক অ্যাকাউন্ট Google Play কে বিভ্রান্ত করতে পারে। Google Play ওয়েবসাইটের মাধ্যমে অ্যাপটি ইনস্টল করলে তা একটি নির্দিষ্ট অ্যাকাউন্টের সাথে যুক্ত হতে বাধ্য করে।</string>
<string name="upgrade_screen_restore_banner_title">ইতিমধ্যে Pro কিনেছেন?</string>
<string name="upgrade_screen_restore_banner_body">মনে হচ্ছে আপনি আগে এই ডিভাইসে Pro-তে আপগ্রেড করেছিলেন। এটি পুনরায় আনলক করতে আপনার কেনাকাটা পুনরুদ্ধার করুন।</string>
<string name="upgrade_screen_restore_success_message">কেনাকাটা পুনরুদ্ধার করা হয়েছে।</string>
@@ -34,7 +23,6 @@
<string name="upgrade_screen_owned_hero_title">আপনার কাছে CAPod Pro আছে!</string>
<string name="upgrade_screen_owned_hero_sub_body">আপনার সাবস্ক্রিপশন দ্বারা আনলক করা হয়েছে। CAPod-কে সমর্থন করার জন্য ধন্যবাদ!</string>
<string name="upgrade_screen_owned_hero_iap_body">আপনার এককালীন ক্রয় দ্বারা আনলক করা হয়েছে। CAPod-কে সমর্থন করার জন্য ধন্যবাদ!</string>
<string name="upgrade_screen_owned_sub_title">বার্ষিক সাবস্ক্রিপশন</string>
<string name="upgrade_screen_owned_sub_renewing_body">আপনার সাবস্ক্রিপশন সক্রিয় এবং নবায়ন হওয়ার জন্য সেট করা আছে। বিলিং Google Play দ্বারা পরিচালিত হয়।</string>
<string name="upgrade_screen_owned_sub_not_renewing_body">আপনার সাবস্ক্রিপশন সক্রিয়, কিন্তু নবায়ন হওয়ার জন্য সেট করা নেই। বর্তমান মেয়াদ শেষ না হওয়া পর্যন্ত Pro উপলব্ধ থাকবে।</string>
<string name="upgrade_screen_owned_iap_title">এককালীন ক্রয়</string>
@@ -46,6 +34,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">এটি একটি আলাদা ক্রয় — এটি আপনার সাবস্ক্রিপশন বাতিল বা ফেরত দেয় না। একই Google অ্যাকাউন্ট ব্যবহার করুন।</string>
<string name="upgrade_screen_sub_still_renewing_title">সাবস্ক্রিপশন এখনও সক্রিয়</string>
<string name="upgrade_screen_sub_still_renewing_message">আপনার সাবস্ক্রিপশন এখনও নবায়ন হওয়ার জন্য সেট করা আছে। প্রথমে Google Play-তে এটি বাতিল করুন, তারপর এককালীন ক্রয়ে পরিবর্তন করুন। এইমাত্র বাতিল করেছেন? Google Play-কে কিছুটা সময় দিন এবং আবার চেষ্টা করুন।</string>
<string name="upgrade_screen_purchase_check_failed_message">Google Play-এর সাথে আপনার ক্রয় যাচাই করা যায়নি। দ্বিগুণ ক্রয় এড়াতে, কিছুক্ষণ পর আবার চেষ্টা করুন।</string>
<string name="upgrade_screen_pending_card_title">পেমেন্ট মুলতুবি আছে</string>
<string name="upgrade_screen_pending_card_body">Google Play এখনো আপনার পেমেন্ট প্রক্রিয়া করছে। পেমেন্ট সম্পন্ন হওয়ার সাথে সাথেই Pro স্বয়ংক্রিয়ভাবে আনলক হবে। কিছু পেমেন্ট পদ্ধতিতে কিছুটা সময় লাগতে পারে। আপনাকে আর কিছু করতে হবে না।</string>
<string name="upgrade_screen_pending_dialog_message">Google Play একটি মুলতুবি পেমেন্টসহ ক্রয় খুঁজে পেয়েছে এবং সেটি এখনো প্রক্রিয়া করছে। পেমেন্ট সম্পন্ন হওয়ার সাথে সাথেই Pro স্বয়ংক্রিয়ভাবে আনলক হবে।</string>
<string name="upgrade_screen_grace_title">আপনার কেনাকাটা নিশ্চিত করা হচ্ছে</string>
<string name="upgrade_screen_grace_body_short">Google Play এখনও আপনার কেনাকাটা নিশ্চিত করেনি। Pro এখনও সক্রিয় আছে — কোনো পদক্ষেপের প্রয়োজন নেই।</string>
<string name="upgrade_screen_grace_body">Google Play অনেকক্ষণ ধরে আপনার কেনাকাটা নিশ্চিত করেনি। Pro এখনও সক্রিয় আছে। আপনি অনলাইনে আছেন এবং কেনাকাটার জন্য ব্যবহৃত Google অ্যাকাউন্ট দিয়ে সাইন ইন করা আছেন কিনা তা নিশ্চিত করুন, তারপর পুনরুদ্ধার করার চেষ্টা করুন।</string>
+4 -12
View File
@@ -1,31 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="upgrades_gplay_unavailable_error">Els serveis de Google Play no estan disponibles.</string>
<string name="upgrades_no_purchases_found_check_account">No s\'ha trobat cap compra. Esteu utilitzant el compte correcte?</string>
<string name="upgrades_gplay_billing_error_label">Error del Google Play</string>
<string name="upgrades_gplay_billing_error_description">S\'ha produït un error al Google Play. Torneu-ho a provar més tard o reinicieu el telèfon.\n\nError: %s</string>
<string name="upgrades_gplay_billing_result_error_label">Error de facturació del Google Play</string>
<string name="upgrades_gplay_billing_result_error_description">S\'ha produït un error en demanar al Google Play els detalls de la vostra compra. Netegeu la memòria cau del Google Play i reinicieu el telèfon.\n\nError %s</string>
<string name="upgrades_gplay_already_owned_label">Ja ho heu comprat?</string>
<string name="upgrades_gplay_already_owned_description">El Google Play informa que ja teniu aquesta actualització, però no s\'ha pogut restaurar. Assegureu-vos que feu servir el compte de Google amb què vau comprar-la. La sincronització del Play Store pot trigar una mica: proveu de reiniciar, esborreu la memòria cau del Google Play o simplement espereu.</string>
<string name="upgrade_restore_action">Restaura la compra</string>
<string name="upgrade_badge_label">Pro</string>
<!-- %1$s is the full localized brand ("CAPod Pro", composed from the app name + postfix) — translate the sentence around it, keep the placeholder. -->
<string name="upgrade_screen_title_template">Obtingueu %1$s</string>
<string name="upgrade_preamble">El CAPod està desenvolupat per una sola persona. La millora desbloqueja funcions addicionals i ajuda a mantenir l\'aplicació activa.</string>
<string name="upgrade_screen_options_description">Les mateixes funcions, preus diferents. La subscripció inclou una prova gratuïta i es pot cancel·lar en qualsevol moment.</string>
<string name="upgrade_screen_options_description_no_trial">Les mateixes funcions, preus diferents. La subscripció es pot cancel·lar en qualsevol moment.</string>
<string name="upgrade_screen_subscription_trial_action">Inicia la prova gratuïta</string>
<string name="upgrade_screen_subscription_action">Subscripció anual</string>
<string name="upgrade_screen_subscription_action_hint">%s/any</string>
<string name="upgrade_screen_iap_action">Compra única</string>
<string name="upgrade_screen_iap_action_hint">Compra única: %s</string>
<string name="upgrade_screen_restore_purchase_action">Restaura la compra</string>
<string name="upgrade_screen_restore_purchase_message">No s\'ha trobat cap compra. Esteu utilitzant el compte correcte?</string>
<string name="upgrade_screen_restore_troubleshooting_msg">Si heu comprat alguna cosa recentment, és possible que el Google Play trigui una estona a sincronitzar-se.</string>
<string name="upgrade_screen_restore_sync_patience_hint">Torneu-ho a provar en uns minuts si la vostra compra no apareix.</string>
<string name="upgrade_screen_restore_multiaccount_hint">Assegureu-vos d\'haver iniciat la sessió amb el mateix compte de Google que heu utilitzat per a la compra.</string>
<string name="upgrade_screen_restore_webinstall_hint">Tenir diversos comptes pot generar confusió al Google Play. La instal·lació de l\'aplicació a través del lloc web del Google Play obliga a associar-lo amb un compte específic.</string>
<string name="upgrade_screen_restore_banner_title">Ja heu comprat Pro?</string>
<string name="upgrade_screen_restore_banner_body">Sembla que ja vau actualitzar al Pro en aquest dispositiu abans. Restaureu la vostra compra per tornar-la a desbloquejar.</string>
<string name="upgrade_screen_restore_success_message">Compra restaurada.</string>
@@ -34,7 +23,6 @@
<string name="upgrade_screen_owned_hero_title">Teniu el CAPod Pro!</string>
<string name="upgrade_screen_owned_hero_sub_body">Desbloquejat per la vostra subscripció. Gràcies per donar suport al CAPod!</string>
<string name="upgrade_screen_owned_hero_iap_body">Desbloquejat per la vostra compra única. Gràcies per donar suport al CAPod!</string>
<string name="upgrade_screen_owned_sub_title">Subscripció anual</string>
<string name="upgrade_screen_owned_sub_renewing_body">La vostra subscripció està activa i configurada per renovar-se. La facturació la gestiona el Google Play.</string>
<string name="upgrade_screen_owned_sub_not_renewing_body">La vostra subscripció està activa, però no configurada per renovar-se. El Pro seguirà disponible fins que acabi el període actual.</string>
<string name="upgrade_screen_owned_iap_title">Compra única</string>
@@ -46,6 +34,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Aquesta és una compra separada: no cancel·la ni reemborsa la vostra subscripció. Feu servir el mateix compte de Google.</string>
<string name="upgrade_screen_sub_still_renewing_title">La subscripció encara està activa</string>
<string name="upgrade_screen_sub_still_renewing_message">La vostra subscripció encara està configurada per renovar-se. Cancel·leu-la primer al Google Play i després canvieu a la compra única. Acabeu de cancel·lar-la? Doneu un moment al Google Play i torneu-ho a provar.</string>
<string name="upgrade_screen_purchase_check_failed_message">No s\'han pogut comprovar les vostres compres amb el Google Play. Per evitar una compra duplicada, torneu-ho a provar d\'aquí a un moment.</string>
<string name="upgrade_screen_pending_card_title">Pagament pendent</string>
<string name="upgrade_screen_pending_card_body">El Google Play encara està processant el vostre pagament. La versió Pro es desbloquejarà automàticament un cop es completi el pagament. Alguns mètodes de pagament poden trigar una estona. No cal que feu res més.</string>
<string name="upgrade_screen_pending_dialog_message">El Google Play ha trobat una compra amb un pagament pendent i encara l\'està processant. La versió Pro es desbloquejarà automàticament un cop es completi el pagament.</string>
<string name="upgrade_screen_grace_title">S\'està confirmant la vostra compra</string>
<string name="upgrade_screen_grace_body_short">El Google Play encara no ha confirmat la vostra compra. El Pro segueix actiu, no cal fer res.</string>
<string name="upgrade_screen_grace_body">Fa temps que el Google Play no confirma la vostra compra. El Pro segueix actiu. Assegureu-vos que teniu connexió i heu iniciat sessió amb el compte de Google que vau fer servir per a la compra, i després proveu de restaurar-la.</string>

Some files were not shown because too many files have changed in this diff Show More