Compare commits
21 Commits
renovate/k
...
bc49730ea5
| Author | SHA1 | Date | |
|---|---|---|---|
| bc49730ea5 | |||
| a1d1894f84 | |||
|
|
a2172dce12 | ||
|
|
4099f66e91 | ||
| 49112b11b3 | |||
| 1218bb523c | |||
| 17600a3905 | |||
|
|
db5104a7d0 | ||
|
|
6306064fc3 | ||
|
|
d5a4761f2c | ||
|
|
66d6681798 | ||
|
|
fb61169656 | ||
|
|
62079fba0b | ||
|
|
cc0fc8edc3 | ||
|
|
62174fcd53 | ||
|
|
55c1b2ac25 | ||
|
|
0ccc610144 | ||
|
|
5d009b7dc0 | ||
|
|
28924daf02 | ||
|
|
e71fb2a625 | ||
|
|
aa5f76fc1b |
@@ -551,12 +551,11 @@ jobs:
|
||||
env:
|
||||
VERSION: ${{ needs.detect.outputs.version }}
|
||||
VERSION_CODE: ${{ needs.detect.outputs.version_code }}
|
||||
# Where the bundle lands. `internal` by default so a release reaches
|
||||
# testers rather than the public, and promotion to production stays a
|
||||
# deliberate human action in the Play Console — the same posture as
|
||||
# holding UI releases for on-device review. Override with the PLAY_TRACK
|
||||
# repo variable once the flow is trusted.
|
||||
PLAY_TRACK: ${{ vars.PLAY_TRACK || 'internal' }}
|
||||
# Where the bundle lands. `production` — the release itself is already the
|
||||
# gate (a bumped versionName only reaches main after on-device review), so
|
||||
# a second manual promotion in the Play Console bought nothing but delay.
|
||||
# Override with the PLAY_TRACK repo variable to stage a release instead.
|
||||
PLAY_TRACK: ${{ vars.PLAY_TRACK || 'production' }}
|
||||
PLAY_RELEASE_STATUS: ${{ vars.PLAY_RELEASE_STATUS || 'completed' }}
|
||||
# Set PLAY_DRY_RUN=true to validate the edit against the API and discard
|
||||
# it instead of committing — used to rehearse the first upload.
|
||||
|
||||
13
CHANGELOG.md
13
CHANGELOG.md
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
- **"Only this event" now actually saves your edit.** On some calendars —
|
||||
including Google ones that still show as on-device, and any local calendar —
|
||||
editing a single occurrence of a repeating event did nothing at all: the scope
|
||||
dialog closed, the edit screen stayed put, and saving again just repeated it.
|
||||
Android can only attach a single-occurrence change to its series once the
|
||||
calendar has been synced at least once, so on those calendars the change had
|
||||
nowhere to go. Calendula now removes that one occurrence from the series and
|
||||
saves the edit as its own event instead, which is what you see either way. A
|
||||
save that does fail also says so for longer, rather than flashing past
|
||||
([#234]).
|
||||
|
||||
## [2.19.3] — 2026-08-22
|
||||
|
||||
### Added
|
||||
@@ -1471,3 +1483,4 @@ automatically, with zero telemetry and no internet permission.
|
||||
[#192]: https://codeberg.org/jlmakiola/calendula/issues/192
|
||||
[#196]: https://codeberg.org/jlmakiola/calendula/issues/196
|
||||
[#214]: https://codeberg.org/jlmakiola/calendula/issues/214
|
||||
[#234]: https://codeberg.org/jlmakiola/calendula/issues/234
|
||||
|
||||
22
README.md
22
README.md
@@ -18,6 +18,8 @@ Reads, writes, and reminds — on top of the system calendar, with zero network
|
||||
<p>
|
||||
<a href="https://f-droid.org/packages/de.jeanlucmakiola.calendula/"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" alt="Get it on F-Droid" height="56"></a>
|
||||
|
||||
<a href="https://play.google.com/store/apps/details?id=de.jeanlucmakiola.calendula"><img src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png" alt="Get it on Google Play" height="56"></a>
|
||||
|
||||
<a href="https://apps.obtainium.imranr.dev/redirect?r=obtainium://add/https://codeberg.org/jlmakiola/calendula"><img src="https://github.com/ImranR98/Obtainium/blob/main/assets/graphics/badge_obtainium.png?raw=true" alt="Get it on Obtainium" height="56"></a>
|
||||
|
||||
<a href="https://ko-fi.com/jeanlucmakiola"><img src="https://storage.ko-fi.com/cdn/brandasset/v2/support_me_on_kofi_badge_beige.png" alt="Support me on Ko-fi" height="56"></a>
|
||||
@@ -86,7 +88,7 @@ Pick whichever channel you already use — they all install the same app:
|
||||
| [Official F-Droid](#f-droid-recommended) | On F-Droid's build schedule | Recommended; no extra setup |
|
||||
| [Self-hosted F-Droid repo](#self-hosted-f-droid-repo-fastest-updates) | Minutes after a release | Fastest; needs the repo added once |
|
||||
| [Codeberg release / Obtainium](#codeberg-release--obtainium) | Per release | Plain APK download, or automated by Obtainium |
|
||||
| [Google Play](#google-play-coming-soon) | — | Coming soon |
|
||||
| [Google Play](#google-play) | Per release | Signed with Google's key — see below |
|
||||
| [Build from source](#build-from-source) | Whenever you build | Full control |
|
||||
|
||||
### F-Droid (recommended)
|
||||
@@ -131,18 +133,14 @@ or do it by hand: *Add App* → paste `https://codeberg.org/jlmakiola/calendula`
|
||||
→ *Add*. Either way, Obtainium tracks the releases and prompts you when a new
|
||||
one appears.
|
||||
|
||||
### Google Play (coming soon)
|
||||
### Google Play
|
||||
|
||||
Calendula is on its way to Google Play as an additional channel. It isn't live
|
||||
yet — this section gets a link once it is. Play builds will be signed with
|
||||
Google's key rather than mine, so switching between Play and any other channel
|
||||
will require an uninstall.
|
||||
Calendula is live on Google Play:
|
||||
**[play.google.com/store/apps/details?id=de.jeanlucmakiola.calendula](https://play.google.com/store/apps/details?id=de.jeanlucmakiola.calendula)**.
|
||||
|
||||
> **Testers wanted.** Play requires a round of closed testing before the app can
|
||||
> go public, and I'm still looking for testers. If you'd like to help, email
|
||||
> **[business@jeanlucmakiola.de](mailto:business@jeanlucmakiola.de)** with the
|
||||
> Google account address you want to use — that address is what I need to add you
|
||||
> to the closed test.
|
||||
Play builds are signed with Google's key rather than mine, so switching between
|
||||
Play and any other channel requires an uninstall (and with it, a fresh start for
|
||||
app settings — your events live in the system calendar and are unaffected).
|
||||
|
||||
### Build from source
|
||||
|
||||
@@ -151,7 +149,7 @@ The build is a plain Gradle build with no proprietary dependencies — see
|
||||
|
||||
<sub>Official F-Droid, the self-hosted repo, and the Codeberg releases all share
|
||||
the same signing key, so you can switch freely between them without
|
||||
reinstalling.</sub>
|
||||
reinstalling. Google Play is the exception — see above.</sub>
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
|
||||
@@ -232,10 +232,15 @@ interface CalendarDataSource {
|
||||
): Long
|
||||
|
||||
/**
|
||||
* Change a single occurrence of a recurring event by inserting a
|
||||
* modified-occurrence exception at [beginMillis] (the occurrence's
|
||||
* `Instances.BEGIN`) carrying [form]'s values; returns the exception
|
||||
* row's `Events._ID`. [allDayReminderTimeMinutes]: see [insertEvent].
|
||||
* Change a single occurrence of a recurring event at [beginMillis] (the
|
||||
* occurrence's `Instances.BEGIN`) to [form]'s values; returns the
|
||||
* `Events._ID` of the row now holding them.
|
||||
*
|
||||
* A series with a `_sync_id` gets a modified-occurrence exception. One
|
||||
* without gets the occurrence excluded from the parent via EXDATE plus a
|
||||
* standalone event carrying the edits — an exception cannot link to its
|
||||
* parent there (Codeberg #234, the same constraint as [deleteOccurrence]).
|
||||
* [allDayReminderTimeMinutes]: see [insertEvent].
|
||||
*/
|
||||
fun updateOccurrence(
|
||||
eventId: Long,
|
||||
@@ -1194,6 +1199,16 @@ class AndroidCalendarDataSource @Inject constructor(
|
||||
form: EventForm,
|
||||
allDayReminderTimeMinutes: Int,
|
||||
): Long {
|
||||
val row = querySeriesRow(eventId)
|
||||
// Deliberately stricter than deleteOccurrence's bare _sync_id check: the
|
||||
// detach path drops the occurrence with EXDATE, which only means anything
|
||||
// on a row that actually recurs. Without an RRULE there is nothing to
|
||||
// exclude from, so such a row keeps the existing path rather than getting
|
||||
// a recurrence set written onto a one-off event. The UI only offers the
|
||||
// scope choice for a recurring event, so neither case is reachable today.
|
||||
if (row.syncId == null && !row.rrule.isNullOrBlank()) {
|
||||
return detachOccurrence(eventId, beginMillis, row, form, allDayReminderTimeMinutes)
|
||||
}
|
||||
// The provider clones the series row and applies these values on top.
|
||||
val values = buildOccurrenceExceptionValues(
|
||||
form = form,
|
||||
@@ -1212,6 +1227,116 @@ class AndroidCalendarDataSource @Inject constructor(
|
||||
return exceptionId
|
||||
}
|
||||
|
||||
/**
|
||||
* "Edit only this event" on a series with **no `_sync_id`**: drop the
|
||||
* occurrence from the parent with EXDATE and insert the edited values as a
|
||||
* standalone event on the same calendar.
|
||||
*
|
||||
* A modified exception attaches to its parent only through `ORIGINAL_SYNC_ID`,
|
||||
* exactly like the cancelled one [deleteOccurrence] documents. With no
|
||||
* `_sync_id` the link never forms: the insert either fails outright or lands
|
||||
* an orphan row, and the parent's expansion collapses — which is why editing
|
||||
* one occurrence of such a series did nothing at all, and occasionally left a
|
||||
* stray copy behind (Codeberg #234). The reporter's calendar was a Google one
|
||||
* that "shows as on-device", i.e. rows the sync adapter had not stamped yet;
|
||||
* Calendula's own local and contact special-date calendars are permanently in
|
||||
* this shape.
|
||||
*
|
||||
* EXDATE + a standalone row needs no parent link, and is what a detached
|
||||
* instance degrades to when there is no `RECURRENCE-ID` to carry it: the user
|
||||
* sees one edited event where the occurrence was, and the rest of the series
|
||||
* untouched.
|
||||
*
|
||||
* What that costs, plainly, because none of it is recoverable later — the
|
||||
* detached row has *no* stored link back to its series, which is the whole
|
||||
* reason this path exists:
|
||||
* - It stops travelling with the series. Moving the series to another
|
||||
* calendar leaves it behind ([moveEvent] copies the master and its
|
||||
* `ORIGINAL_ID` children; this is neither), and deleting the whole series
|
||||
* leaves it standing where an exception row would have gone with the parent.
|
||||
* - Its EXDATE hole is an absolute instant. Editing the series' *time* for all
|
||||
* events moves the generated instances but not the hole, so the occurrence
|
||||
* comes back alongside the detached copy. That staleness predates this path
|
||||
* — a #47 delete resurrects the same way — but a duplicate is a louder
|
||||
* symptom than a resurrection, and it wants fixing at the series-update end.
|
||||
* - It is built from the form, not cloned from the parent, so columns the form
|
||||
* doesn't model are dropped rather than inherited: `ORGANIZER`, `STATUS`,
|
||||
* and the organizer/resource attendee rows [reconcileAttendees] otherwise
|
||||
* preserves. Same limitation as [moveEvent]. Rare on the calendars that
|
||||
* reach this path, which are locally authored, but not impossible.
|
||||
*
|
||||
* Order is deliberate. The insert goes first, so a failure there leaves the
|
||||
* series completely untouched (the same discipline as
|
||||
* [updateEventFromOccurrence]). If the EXDATE update then fails, the new row
|
||||
* is a visible duplicate of an occurrence that is still in the series, so it
|
||||
* is rolled back before the failure surfaces — better a save the user can
|
||||
* retry than a silent duplicate. The reverse order would risk the opposite:
|
||||
* an occurrence excluded from the series with no replacement, i.e. an edit
|
||||
* that quietly deletes.
|
||||
*/
|
||||
private fun detachOccurrence(
|
||||
eventId: Long,
|
||||
beginMillis: Long,
|
||||
row: SeriesRow,
|
||||
form: EventForm,
|
||||
allDayReminderTimeMinutes: Int,
|
||||
): Long {
|
||||
// Already detached (or deleted): the series no longer contains this
|
||||
// occurrence, so there is nothing here to edit. Reached from a stale
|
||||
// screen still pointing at the parent — without this the EXDATE merge
|
||||
// would fold the repeat away, the update would still report a changed
|
||||
// row, and the save would quietly leave a *second* standalone copy.
|
||||
if (exdateContains(row.exdate, beginMillis, isAllDay = row.allDay != 0)) {
|
||||
throw NoSuchEventException(eventId)
|
||||
}
|
||||
// Carries the form's reminders, guests and colour like any new event —
|
||||
// and a fresh UID, because the detached row really is a separate event
|
||||
// now: sharing the parent's would collide with it in .ics restore dedup.
|
||||
val detachedId = insertEvent(form.toDetachedOccurrence(), allDayReminderTimeMinutes)
|
||||
val values = buildOccurrenceExdateValues(
|
||||
existingExdate = row.exdate,
|
||||
occurrenceMillis = beginMillis,
|
||||
dtStartMillis = row.dtStartMillis,
|
||||
rrule = row.rrule,
|
||||
duration = row.duration,
|
||||
timezone = row.timezone,
|
||||
allDay = row.allDay,
|
||||
)
|
||||
// Rows touched, not occurrences excluded: this is 1 whenever the series
|
||||
// row still exists. It catches the row disappearing under us, not an
|
||||
// EXDATE the provider's expansion fails to match — that would report
|
||||
// success and leave the duplicate. Same rollback idiom as moveEvent.
|
||||
val updatedRows = try {
|
||||
resolver.update(
|
||||
ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventId),
|
||||
values.toContentValues(), null, null,
|
||||
)
|
||||
} catch (t: Throwable) {
|
||||
rollBackDetached(detachedId)
|
||||
throw t
|
||||
}
|
||||
if (updatedRows == 0) {
|
||||
rollBackDetached(detachedId)
|
||||
throw WriteFailedException(
|
||||
"exdate occurrence for edit, event id=$eventId begin=$beginMillis",
|
||||
)
|
||||
}
|
||||
return detachedId
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo the standalone row [detachOccurrence] inserted before its EXDATE
|
||||
* update failed. Best effort: the caller is already throwing, and a rollback
|
||||
* that itself fails must not replace the real failure with a confusing one.
|
||||
* The worst case is the duplicate we were trying to avoid, which the user can
|
||||
* see and delete — never a lost occurrence.
|
||||
*/
|
||||
private fun rollBackDetached(detachedId: Long) {
|
||||
runCatching { deleteEvent(detachedId) }.onFailure {
|
||||
Log.w(TAG, "Failed to roll back detached occurrence $detachedId", it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateEventFromOccurrence(
|
||||
eventId: Long,
|
||||
beginMillis: Long,
|
||||
|
||||
@@ -243,6 +243,26 @@ internal fun buildOccurrenceExceptionValues(
|
||||
putAll(eventColorColumns(form.colorKey, form.color))
|
||||
}
|
||||
|
||||
/**
|
||||
* The form as a **detached occurrence**: the same edited values, with the
|
||||
* series rule dropped so [buildEventInsertValues] writes a standalone one-off
|
||||
* row (DTSTART + DTEND, no RRULE/DURATION) at the occurrence's own times.
|
||||
*
|
||||
* This is the "edit only this event" shape for a series with **no `_sync_id`**,
|
||||
* where an exception row can't be used at all — see [buildOccurrenceExdateValues]
|
||||
* for why the parent link never forms. The occurrence is dropped from the parent
|
||||
* with EXDATE and re-created as its own event, which is what a detached instance
|
||||
* degrades to without a `RECURRENCE-ID` to carry it.
|
||||
*
|
||||
* Dropping the rule mirrors what the exception path gets for free: the provider
|
||||
* clears the RRULE it cloned from the parent when an exception carries
|
||||
* DTSTART + DURATION ([buildOccurrenceExceptionValues]). Here nothing is cloned,
|
||||
* so the rule has to be stripped by hand — leaving it on would insert a second
|
||||
* *series* overlapping the first, which is the duplication this path exists to
|
||||
* avoid.
|
||||
*/
|
||||
internal fun EventForm.toDetachedOccurrence(): EventForm = copy(rrule = null)
|
||||
|
||||
/**
|
||||
* Raw provider snapshot of a master/one-off Events row, enough to re-insert it
|
||||
* verbatim on another calendar (a calendar move is copy+delete — `CALENDAR_ID`
|
||||
@@ -435,6 +455,26 @@ internal fun buildOccurrenceExdateValues(
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether [existingExdate] already excludes the occurrence at [occurrenceMillis]
|
||||
* — i.e. this occurrence has already been dropped from the series (deleted, or
|
||||
* detached into its own event).
|
||||
*
|
||||
* Guards the detach path against running twice for the same occurrence, which a
|
||||
* stale detail screen still pointing at the parent can otherwise reach. The
|
||||
* EXDATE merge folds the repeat away silently and the parent update still
|
||||
* reports one row changed, so without this check the second save would leave a
|
||||
* *second* standalone copy and call it a success.
|
||||
*/
|
||||
internal fun exdateContains(
|
||||
existingExdate: String?,
|
||||
occurrenceMillis: Long,
|
||||
isAllDay: Boolean,
|
||||
): Boolean {
|
||||
val stamp = formatExdateStamp(occurrenceMillis, isAllDay)
|
||||
return existingExdate?.split(',')?.any { it.trim() == stamp } == true
|
||||
}
|
||||
|
||||
/**
|
||||
* One EXDATE entry for the occurrence starting at [occurrenceMillis]. Both forms
|
||||
* are UTC: the provider stores an all-day DTSTART at UTC midnight, so its date
|
||||
|
||||
@@ -64,6 +64,7 @@ import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SegmentedButton
|
||||
import androidx.compose.material3.SegmentedButtonDefaults
|
||||
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
||||
import androidx.compose.material3.SnackbarDuration
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Surface
|
||||
@@ -264,13 +265,17 @@ fun EventEditScreen(
|
||||
viewModel.reset()
|
||||
onSaved()
|
||||
}
|
||||
// A failed save leaves the user on a form that looks exactly as it
|
||||
// did, so the snackbar is the only sign anything happened — it gets
|
||||
// the long duration rather than the default flash (Codeberg #234:
|
||||
// the failure read as "nothing happens at all").
|
||||
SaveUiState.Failed -> {
|
||||
viewModel.consumeSaveResult()
|
||||
snackbarHostState.showSnackbar(saveFailedMessage)
|
||||
snackbarHostState.showSnackbar(saveFailedMessage, duration = SnackbarDuration.Long)
|
||||
}
|
||||
SaveUiState.NeedsPermission -> {
|
||||
viewModel.consumeSaveResult()
|
||||
snackbarHostState.showSnackbar(writeDeniedMessage)
|
||||
snackbarHostState.showSnackbar(writeDeniedMessage, duration = SnackbarDuration.Long)
|
||||
}
|
||||
// AwaitingScope/AwaitingConflict/Gone render as dialogs below.
|
||||
else -> Unit
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package de.jeanlucmakiola.calendula.ui.edit
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
@@ -56,6 +57,8 @@ import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val TAG = "EventEdit"
|
||||
|
||||
/**
|
||||
* Where a prefilled [EventEditViewModel.openImported] form came from. The sources
|
||||
* want different reminder handling (#49), and differ in whether they own the
|
||||
@@ -751,7 +754,16 @@ class EventEditViewModel @Inject constructor(
|
||||
throw e
|
||||
} catch (e: SecurityException) {
|
||||
SaveUiState.NeedsPermission
|
||||
} catch (e: NoSuchEventException) {
|
||||
// The write found the event (or the occurrence) already gone —
|
||||
// the same answer the pre-check gives, and a far better one than
|
||||
// a bare "couldn't save" for something that no longer exists.
|
||||
SaveUiState.Gone
|
||||
} catch (e: Exception) {
|
||||
// The user only gets a generic snackbar, so without this a
|
||||
// failed write leaves no trace at all to report (Codeberg #234).
|
||||
// Scope and event id only — never the form's content.
|
||||
Log.w(TAG, "Save failed (scope=$scope, eventId=${target?.eventId})", e)
|
||||
SaveUiState.Failed
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<string name="settings_theme_system">النظام</string>
|
||||
<string name="settings_theme_light">فاتح</string>
|
||||
<string name="settings_theme_dark">داكن</string>
|
||||
<string name="month_open_menu">افتح القائمة</string>
|
||||
<string name="month_open_menu">فتح القائمة</string>
|
||||
<string name="month_more_actions">المزيد من الإجراءات</string>
|
||||
<string name="event_detail_edit">تعديل</string>
|
||||
<string name="event_detail_delete">حذف</string>
|
||||
@@ -176,7 +176,7 @@
|
||||
<string name="reminder_at_time">في وقت الحدث</string>
|
||||
<string name="event_untitled">(بدون عنوان)</string>
|
||||
<string name="reminder_channel_name">تذكيرات الحدث</string>
|
||||
<string name="reminder_channel_description">الإشعارات في أوقات التذكير الخاصه بأحداثك</string>
|
||||
<string name="reminder_channel_description">الإشعارات في أوقات التذكير بأحداثك</string>
|
||||
<string name="reminder_onboarding_title">لا تفوّت أي حدث أبدًا</string>
|
||||
<string name="reminder_onboarding_body">أندرويد لا يعرض تذكيرات الأحداث من نفسه — بل يجب أن يقوم تطبيق تقويم بذلك. دع Calendula يتولى هذه المهمة.</string>
|
||||
<string name="reminder_benefit_delivery_body">كل تذكير لأحداثك يصِل كإشعار، في الوقت المحدد تمامًا.</string>
|
||||
@@ -238,7 +238,7 @@
|
||||
<string name="settings_time_format_24h">٢٤-ساعة (١٤:٠٠)</string>
|
||||
<string name="agenda_range_day">اليوم</string>
|
||||
<string name="settings_agenda_show_today">إظهار اليوم دائمًا</string>
|
||||
<string name="settings_agenda_show_today_hint">اترك اليوم في أعلى الجدول و ودجته، حتى بعد عدم وجود أي شيء اليوم.</string>
|
||||
<string name="settings_agenda_show_today_hint">اترك اليوم في أعلى الجدول و أداته المصغرة، حتى بعد عدم وجود أي شيء اليوم.</string>
|
||||
<string name="agenda_range_this_month">هذا الشهر</string>
|
||||
<string name="agenda_range_this_week">هذا الأسبوع</string>
|
||||
<string name="settings_past_events_show">إظهار</string>
|
||||
@@ -249,7 +249,7 @@
|
||||
<string name="event_edit_timezone_all">جميع المناطق الزمنية</string>
|
||||
<string name="event_edit_timezone_none">لا منطقة زمنية تطابق ”%1$s“</string>
|
||||
<string name="event_edit_timezone_local_time">%1$s توقيتك</string>
|
||||
<string name="event_edit_timezone_recent">الأحدث</string>
|
||||
<string name="event_edit_timezone_recent">الأخيرة</string>
|
||||
<string name="event_edit_recurrence_incomplete">أدخل رقمًا من ١ إلى ٩٩٩</string>
|
||||
<plurals name="duration_minutes">
|
||||
<item quantity="zero">(%d) لا دقائق</item>
|
||||
@@ -286,7 +286,7 @@
|
||||
<string name="agenda_span_starts">يبدأ %1$s</string>
|
||||
<string name="agenda_span_ends">ينتهي %1$s</string>
|
||||
<string name="today_jump_action">الانتقال إلى اليوم</string>
|
||||
<string name="widget_refresh">تحديث</string>
|
||||
<string name="widget_refresh">إعادة تحميل</string>
|
||||
<string name="settings_app_name">اسم التطبيق</string>
|
||||
<string name="settings_dim_completed">تعتيم الأحداث المكتملة</string>
|
||||
<string name="settings_dim_completed_summary">تعتيم الأحداث التي انتهت بالفعل في عرض الشهر والأسبوع</string>
|
||||
@@ -335,8 +335,8 @@
|
||||
<item quantity="many">%d يوم</item>
|
||||
<item quantity="other">%d يوم</item>
|
||||
</plurals>
|
||||
<string name="settings_agenda_range_bar_hint">اعرض شريطًا في الجزء العلوي من الجدول يحدد التواريخ المعروضة، مع زر لتبديل النطاق للجلسة</string>
|
||||
<string name="settings_agenda_range_bar">شريط النطاق</string>
|
||||
<string name="settings_agenda_range_bar_hint">اعرض شريطًا في الجزء العلوي من الجدول يحدد التواريخ المعروضة، مع زر لتبديل المدى للجلسة</string>
|
||||
<string name="settings_agenda_range_bar">شريط المدى</string>
|
||||
<string name="settings_month_header">عرض الشهر</string>
|
||||
<string name="month_style_continuous">تمرير الشهور</string>
|
||||
<string name="month_style_dense">أسابيع سلسة</string>
|
||||
@@ -424,7 +424,7 @@
|
||||
<string name="settings_views_subtitle">العرض، التخطيط، الترتيب</string>
|
||||
<string name="settings_event_form_subtitle">الخانات الافتراضية و السلوك</string>
|
||||
<string name="settings_notifications_subtitle">التذكيرات و التسليم</string>
|
||||
<string name="settings_special_dates_subtitle">أعياد ميلاد جهات الاتصال و احتفالات ذكرى</string>
|
||||
<string name="settings_special_dates_subtitle">أعياد ميلاد جهات الاتصال و الذكرات السنوية</string>
|
||||
<string name="settings_special_dates_type_birthday">أعياد الميلاد</string>
|
||||
<string name="settings_special_dates_type_custom">تواريخ أخرى</string>
|
||||
<string name="settings_license">الترخيص</string>
|
||||
@@ -432,7 +432,7 @@
|
||||
<string name="settings_special_dates_disable_confirm">إيقاف</string>
|
||||
<string name="settings_special_dates_sync_now">زامن الآن</string>
|
||||
<string name="settings_special_dates_never_synced">لم تتم المزامنة بعد</string>
|
||||
<string name="settings_special_dates_paused_hint">لم يعد بإمكان Calendula قراءة جهات الاتصال الخاصة بك، لذلك لا يتم تحديث هذه التقويمات.</string>
|
||||
<string name="settings_special_dates_paused_hint">لم يعد بإمكان Calendula قراءة جهات اتصالك، لذلك لا يتم تحديث هذه التقويمات.</string>
|
||||
<string name="calendars_manage_in_app">الإدارة في التطبيق</string>
|
||||
<string name="calendars_enable_all">تفعيل الكل</string>
|
||||
<string name="calendars_disable_all">تعطيل الكل</string>
|
||||
@@ -500,11 +500,11 @@
|
||||
<string name="settings_qs_tile">إضافة مربع الإعدادات السريعة</string>
|
||||
<string name="import_button">استيراد</string>
|
||||
<string name="crash_dialog_dismiss">ليس الآن</string>
|
||||
<string name="crash_report_copied">تم نسخ التقرير إلى الحافظة</string>
|
||||
<string name="crash_report_open_failed">تعذّر فتح متتبع المشكلات. التقرير موجود في الحافظة الخاصة بك.</string>
|
||||
<string name="crash_report_copied">تم نسخ التقرير إلى حافظتك</string>
|
||||
<string name="crash_report_open_failed">تعذّر فتح متتبع المشكلات. التقرير موجود في حافظتك.</string>
|
||||
<string name="crash_report_issue_title">تقرير العطل</string>
|
||||
<string name="crash_report_clip_label">%1$s تقرير العطل</string>
|
||||
<string name="crash_report_body_paste">_(كان التقرير طويلًا جدًا لهذا الرابط — الصقه من الحافظة هنا.)_</string>
|
||||
<string name="crash_report_body_paste">_(كان التقرير طويلًا جدًا لهذا الرابط — الصقه من حافظتك هنا.)_</string>
|
||||
<string name="special_dates_calendar_birthday">أعياد الميلاد</string>
|
||||
<string name="special_dates_calendar_custom">التواريخ الخاصة</string>
|
||||
<string name="event_edit_recurrence_next_none">هذه القاعدة لا تتكرر أبدًا</string>
|
||||
@@ -515,18 +515,18 @@
|
||||
<string name="month_split_expand">إظهار الشهر بأكمله</string>
|
||||
<string name="settings_week_day_header">الأسبوع و اليوم</string>
|
||||
<string name="settings_default_view_hint">طريقة العرض التي يفتحها Calendula عندما تقوم ببدءه.</string>
|
||||
<string name="settings_week_start_hint">اليوم الذي يبدأ به كل أسبوع، في جميع طرق العرض والودجتز.</string>
|
||||
<string name="settings_week_start_hint">اليوم الذي يبدأ به كل أسبوع، في جميع طُرق العرض والأدوات المصغّرة.</string>
|
||||
<string name="settings_time_format_hint">كيف الأوقات تُكتب في جميع أنحاء التطبيق. تلقائيًا يتبع إعداد نظامك.</string>
|
||||
<string name="settings_past_events_hint">ما الذي يفعله جدول بالأحداث التي انتهت بالفعل.</string>
|
||||
<string name="calendars_visibility_a11y">إظهار \"%1$s\"</string>
|
||||
<string name="calendars_visibility_notice_title">بعض التقويمات متوقفة</string>
|
||||
<string name="calendars_visibility_notice_message">Calendula يعرض التقويمات التي تم تشغيلها لهذا الجهاز، وبعض من خاصتك متوقفة. لرؤية أحداثهم، قم بتشغيلهم ضمن الإعدادات ← التقويمات.</string>
|
||||
<string name="calendar_picker_missing_title">تفتقد تقويمًا؟</string>
|
||||
<string name="calendar_picker_missing_summary">قد يكون متوقفًا عن التشغيل، للقراءة فقط أو مملوءًا من جهات الاتصال الخاصة بك — يمكنك إدارة تقويماتك هنا.</string>
|
||||
<string name="calendar_picker_missing_summary">قد يكون متوقفًا عن التشغيل، للقراءة فقط أو مملوءًا من جهات اتصالك — يمكنك إدارة تقويماتك هنا.</string>
|
||||
<string name="calendars_state_read_only">للقراءة فقط</string>
|
||||
<string name="calendars_state_not_synced">لم تتم المزامنة مع هذا الجهاز</string>
|
||||
<string name="calendars_state_managed">تم ملؤه من جهات اتصالك</string>
|
||||
<string name="calendars_managed_delete_locked">تم ملء هذا التقويم من جهات الاتصال الخاصة بك، لذلك سيقوم Calendula بإنشائه مرة أخرى في المزامنة التالية. قم بإيقاف تشغيل التواريخ الخاصة ضمن الإعدادات ← التواريخ الخاصة لحذفها.</string>
|
||||
<string name="calendars_managed_delete_locked">تم ملء هذا التقويم من جهات اتصالك، لذلك سيقوم Calendula بإنشائه مرة أخرى في المزامنة التالية. قم بإيقاف تشغيل التواريخ الخاصة ضمن الإعدادات ← التواريخ الخاصة لحذفها.</string>
|
||||
<string name="duration_custom_max">كحد أقصى %1$s</string>
|
||||
<string name="settings_calendar_duration_inherits">الافتراضي (%1$s)</string>
|
||||
<string name="settings_calendar_duration_use_default">استخدام المدة الافتراضية (%1$s)</string>
|
||||
@@ -656,26 +656,53 @@
|
||||
<string name="settings_special_dates_calendar_hint">اضبط لون كل تقويم وإمكانية ظهوره في إعدادات التقويمات.</string>
|
||||
<string name="calendars_visibility_hint">قم بإيقاف تقويم لإخفائه على هذا الجهاز — تختفي أحداثه من التطبيق ويتوقف عن تذكيرك. هذا هو مفتاح التبديل نفسه الذي تستخدمه تطبيقات التقويم الأخرى خاصتك، لذلك تقوم بإخفائه أيضًا. لا شيء يتم حذفه، ولا يتأثر أي جهاز آخر، ويمكنك إعادة تشغيله من هنا في أي وقت.</string>
|
||||
<string name="settings_allday_reminder_fires_at">يتم الإشعار في %1$s</string>
|
||||
<string name="settings_agenda_widget_range">نطاق ودجت الجدول</string>
|
||||
<string name="settings_widget_size">حجم ودجت الجدول</string>
|
||||
<string name="settings_widgets_subtitle">ودجت الجدول، عنصر الإعدادات السريعة</string>
|
||||
<string name="settings_agenda_widget_range">مدى أداة الجدول</string>
|
||||
<string name="settings_widget_size">حجم أداة الجدول</string>
|
||||
<string name="settings_widgets_subtitle">أداة الجدول، عنصر الإعدادات السريعة</string>
|
||||
<string name="settings_drag_to_reschedule">السحب لتعديل الموعد</string>
|
||||
<string name="settings_drag_to_reschedule_summary">انقل حدثًا عن طريق سحبه إلى يوم أو وقت آخر</string>
|
||||
<string name="event_move_blocked_series_end">لا يمكن نقل حدث بعد نهاية سلسلته</string>
|
||||
<string name="crash_dialog_report">تقرير</string>
|
||||
<string name="settings_quick_switch_hint">اختر أي طُرق العرض التي يتم التنقل بينها باستخدام الزر الموجود في أعلى اليمين، واسحب لإعادة ترتيبها. تظل طرق العرض التي تم إيقاف تشغيلها متاحة من قائمة التنقل.</string>
|
||||
<string name="settings_quick_switch_hint">اختر أي طُرق العرض التي يتم التنقل بينها باستخدام الزر في أعلى اليمين، واسحب لإعادة ترتيبهم. تظل طُرق العرض التي تم إيقافها متاحة من قائمة التنقل.</string>
|
||||
<string name="calendars_account_from_source">%1$s (%2$s)</string>
|
||||
<string name="duration_hours_minutes">%1$s %2$s</string>
|
||||
<string name="settings_widgets_hint">إعدادات لوجدتز الشاشة الرئيسية وعنصر لوحة الإعدادات السريعة. أضف وجدت عن طريق الضغط لفترة طويلة على شاشتك الرئيسية.</string>
|
||||
<string name="settings_widgets_hint">إعدادات لأدوات الشاشة الرئيسية وعنصر الإعدادات السريعة. أضف أداة عن طريق الضغط لفترة طويلة على شاشتك الرئيسية.</string>
|
||||
<string name="import_warning_attendees">لم يتم استيراد قوائم الضيوف.</string>
|
||||
<string name="settings_reliable_delivery_exempt">معفى من تحسين البطارية — تصل التذكيرات في الوقت المحدد.</string>
|
||||
<string name="settings_reliable_delivery">توصيل موثوق</string>
|
||||
<string name="settings_allday_reminder_time_hint">تنطلق التذكيرات لأحداث التي تستمر طوال اليوم عند %1$s</string>
|
||||
<string name="agenda_range_override_hint">فقط للآن — يعيد التعيين إلى المدى المحفوظ الخاص بك عند إعادة فتح Calendula.</string>
|
||||
<string name="agenda_range_override_hint">فقط للآن — يعيد التعيين للمدى المحفوظ خاصتك عند إعادة فتح Calendula.</string>
|
||||
<string name="settings_past_events_sample_morning">مراجعة الفريق</string>
|
||||
<string name="settings_past_events_sample_midday">غداء مع روبن</string>
|
||||
<string name="settings_past_events_sample_evening">ممارسة الجوقة</string>
|
||||
<string name="settings_special_dates_last_synced">آخر مزامنة %1$s</string>
|
||||
<string name="settings_special_dates_disable_type_message">يؤدي هذا لحذف التقويم ”%1$s“ وأحداثه. سيتم فقدان أي تذكيرات أو ملاحظات أضفتها إليه.</string>
|
||||
<string name="special_dates_default_title_birthday">عيد ميلاد {name} ({year})</string>
|
||||
<string name="event_move_action">نقل…</string>
|
||||
<string name="settings_font_specimen_heading">الخميس، 14 مايو</string>
|
||||
<string name="settings_font_specimen_body">مراجعة الفريق في 10:00، ثم الغداء مع روبن في المقهى في ماركتبلاتز.</string>
|
||||
<string name="settings_section_widgets">الأدوات المصغّرة والعناصر</string>
|
||||
<string name="settings_widget_size_hint">مدى حجم النص الذي يعرضه جدول أداة الشاشة الرئيسية. أداة الشهر ليس لديه إعداد — تتناسب شبكته دائمًا نفسه للمساحة التي تمنحها إيّاه.</string>
|
||||
<string name="settings_agenda_widget_range_hint">كم مدى يعرض جدول أداة الشاشة الرئيسية الأحداث.</string>
|
||||
<string name="settings_agenda_range_hint">كم مدى يعرض شاشة الجدول الأحداث.</string>
|
||||
<string name="app_name">Calendula</string>
|
||||
<string name="font_atkinson_hyperlegible">Atkinson Hyperlegible</string>
|
||||
<string name="settings_special_dates_show_year">إظهار السنة</string>
|
||||
<string name="settings_special_dates_show_year_hint">تضمين {year} في العناوين عندما يكون معروفًا</string>
|
||||
<string name="special_dates_default_title_custom">{name}</string>
|
||||
<string name="special_dates_default_title_anniversary">ذكرى {name} السنوية ({year})</string>
|
||||
<string name="special_dates_calendar_anniversary">الذكرات السنوية</string>
|
||||
<string name="crash_dialog_message">أُغلق %1$s بشكل غير متوقع في المرة الأخيرة. يمكنك المساعدة في إصلاحه عن طريق إرسال هذا التقرير كمشكلة. سيبقى على جهازك حتى تختار مشاركته، ولا يتضمن أي بيانات شخصية أو محتوى تقويم — فقط التفاصيل الفنية أدناه.</string>
|
||||
<string name="font_lora">Lora</string>
|
||||
<string name="font_jetbrains_mono">JetBrains Mono</string>
|
||||
<string name="onboarding_backup_benefit_folder_title">مجلد تختاره</string>
|
||||
<string name="import_warning_timezone">منطقة زمنية غير معروفة ترجع إلى الخاصة بجهازك.</string>
|
||||
<string name="settings_calendar_reminders_managed_hint">تعيين في تواريخ جهات الاتصال الخاصة</string>
|
||||
<string name="settings_special_dates_disable_all_message">يؤدي هذا إلى حذف تقويمات تواريخ جهات الاتصال وأحداثها. سيتم فقدان أي تذكيرات أو ملاحظات أضفتها إليها.</string>
|
||||
<string name="settings_special_dates_type_anniversary">الذكرات السنوية</string>
|
||||
<string name="settings_special_dates_template_hint">استخدم {name} لجهة الاتصال و{year} للسنة (سنة الميلاد، أو سنة بدء الذكرى السنوية؛ مخفية عندما تكون غير معروفة).</string>
|
||||
<string name="settings_special_dates_template">صيغة العنوان</string>
|
||||
<string name="settings_calendar_reminders_hint">تجاوز الافتراضي لكل تقويم — بشكل منفصل للمحدد بوقت وأحداث طوال اليوم. يمكن لتقويم الاحتفاظ بالافتراضي، إسقاطه أو تعيين خاصّته.</string>
|
||||
<string name="event_move_occurrence_only">سيؤدي نقل السلسلة بأكملها لتغيير الأيام التي تقع فيها، لذلك يمكن نقل هذا الحدث فقط.</string>
|
||||
<string name="agenda_empty_title">أنت على اطلاع</string>
|
||||
</resources>
|
||||
|
||||
149
app/src/main/res/values-be/strings.xml
Normal file
149
app/src/main/res/values-be/strings.xml
Normal file
@@ -0,0 +1,149 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="backup_interval_units">
|
||||
<item>Minutes</item>
|
||||
<item>Hours</item>
|
||||
<item>Days</item>
|
||||
<item>Weeks</item>
|
||||
</string-array>
|
||||
<string name="app_tagline">Сучасны каляндар.</string>
|
||||
<string name="state_loading">Загрузка…</string>
|
||||
<string name="state_retry">Паўтарыць спробу</string>
|
||||
<string name="state_failure_unknown">Узнікла нейкая праблема.</string>
|
||||
<string name="state_failure_permission">Патрабуецца доступ да календара.</string>
|
||||
<string name="state_failure_permission_action">Дазволіць доступ</string>
|
||||
<string name="state_failure_no_calendars">Календары не сканфігураваныя.</string>
|
||||
<string name="state_failure_no_calendars_action">Адкрыць сыстэмныя налады календароў</string>
|
||||
<string name="state_failure_provider">Немагчыма прачытаць каляндар.</string>
|
||||
<string name="permission_rationale_title">Прагляд усіх падзеяў у прыгожым выглядзе</string>
|
||||
<string name="permission_rationale_body">Для паказу і кіраваньня падзеямі Calendula патрабуецца доступ да вашага календара.</string>
|
||||
<string name="permission_request_button">Дазволіць доступ да календара</string>
|
||||
<string name="permission_denied_title">Няма доступу да календара</string>
|
||||
<string name="permission_denied_body">Calendula ня можа паказваць падзеі бяз доступу да календара.</string>
|
||||
<string name="permission_open_settings_button">Адкрыць сыстэмныя налады</string>
|
||||
<string name="permission_retry_button">Паўтарыць спробу</string>
|
||||
<string name="permission_benefit_private_title">Распрацавана з акцэнтам на прыватнасьць</string>
|
||||
<string name="permission_benefit_private_body">Бяз доступу да Інтэрнэту, без тэлемэтрыі — нічога і ніколі не пакідае ваш тэлефон.</string>
|
||||
<string name="permission_benefit_sync_title">Усе вашы календары разам</string>
|
||||
<string name="permission_benefit_sync_body">Google, CalDAV, лякальныя — паказваюцца ўсе, сынхранізаваныя з прыладай.</string>
|
||||
<string name="permission_benefit_privacy_title">Ніякага адсочваньня, ніколі</string>
|
||||
<string name="permission_benefit_privacy_body">Без тэлемэтрыі, без аналітыкі, без рэклямы.</string>
|
||||
<string name="permission_privacy_footnote">Усё застаецца на прыладзе, доступ да Інтэрнэту не патрабуецца</string>
|
||||
<string name="month_prev">Папярэдні месяц</string>
|
||||
<string name="month_next">Наступны месяц</string>
|
||||
<string name="month_today_action">Сёньня</string>
|
||||
<string name="month_more_actions">Іншыя дзеяньні</string>
|
||||
<string name="month_open_menu">Адкрыць мэню</string>
|
||||
<string name="month_action_settings">Налады</string>
|
||||
<string name="month_a11y_today_prefix">Сёньня</string>
|
||||
<string name="week_today_action">Гэты тыдзень</string>
|
||||
<string name="week_number_label">Тдз.</string>
|
||||
<string name="day_today_action">Сёньня</string>
|
||||
<string name="event_detail_back">Назад</string>
|
||||
<string name="event_detail_edit">Праўка</string>
|
||||
<string name="event_detail_delete">Выдаліць</string>
|
||||
<string name="event_detail_share">Падзяліцца</string>
|
||||
<string name="event_detail_duplicate">Дублікат</string>
|
||||
<string name="event_share_chooser_title">Падзяліцца падзеяй</string>
|
||||
<string name="event_share_failed">Не атрымалася падзяліцца гэтай падзеяй.</string>
|
||||
<string name="event_delete_title">Выдаліць падзею?</string>
|
||||
<string name="event_delete_body">Падзея выдаленая з вашага календара і з усіх прыладаў, зь якімі адбываецца сынхранізацыя.</string>
|
||||
<string name="event_delete_recurring_title">Выдаліць пэрыядычную падзею</string>
|
||||
<string name="event_delete_option_occurrence">Толькі гэтую падзею</string>
|
||||
<string name="event_delete_option_following">Гэтую і ўсе наступныя падзеі</string>
|
||||
<string name="event_delete_option_series">Усе падзеі ў пасьлядоўнасьці</string>
|
||||
<string name="event_edit_recurring_title">Рэдагаваць пэрыядычную падзею</string>
|
||||
<string name="event_delete_failed">Не атрымалася выдаліць падзею</string>
|
||||
<string name="event_delete_write_denied">Для выдаленьня падзеяў Calendula патрабуецца дазвол на запіс</string>
|
||||
<string name="event_move_action">Перанесці…</string>
|
||||
<string name="event_move_recurring_title">Перанесці пэрыядычную падзею</string>
|
||||
<string name="event_move_occurrence_only">Перанос усёй пасьлядоўнасьці зьменіць дні, на якія яна прыпадае, таму перанесці можна толькі гэтую падзею.</string>
|
||||
<string name="event_move_done">Перанесена на %1$s</string>
|
||||
<string name="event_move_undo">АдрабІць</string>
|
||||
<string name="event_move_undone">Перанос скасаваны</string>
|
||||
<string name="event_move_failed">Не атрымалася перанесьці падзею</string>
|
||||
<string name="event_move_write_denied">Для пераносу падзеяў Calendula патрабуецца дазвол на запіс</string>
|
||||
<string name="event_move_gone">Гэтай падзеі больш не існуе</string>
|
||||
<string name="event_move_blocked_series_end">Нельга перанесьці падзею на дату пасьля заканчэньня яе пасьлядоўнасьці</string>
|
||||
<string name="dialog_cancel">Адмена</string>
|
||||
<string name="dialog_ok">ОК</string>
|
||||
<string name="event_edit_new_title">Новая падзея</string>
|
||||
<string name="event_edit_close">Закрыць</string>
|
||||
<string name="event_edit_save">Захаваць</string>
|
||||
<string name="event_edit_title_hint">Дадаць загаловак</string>
|
||||
<string name="event_edit_managed_hint">Кіруецца «%1$s» — загаловак, дата і паўтор сынхранізуюцца з вашымі кантактамі. Напаміны, месцазнаходжаньне і нататкі даступныя вам для рэдагаваньня.</string>
|
||||
<string name="event_edit_starts">Пачатак</string>
|
||||
<string name="event_edit_ends">Заканчэньне</string>
|
||||
<string name="event_edit_error_end_before_start">Заканчваецца раней, чым пачынаецца</string>
|
||||
<string name="event_edit_error_no_calendar">Няма календара, у які дазволены запіс</string>
|
||||
<string name="event_edit_save_failed">Не атрымалася захаваць падзею</string>
|
||||
<string name="event_edit_write_denied">Для стварэньня падзеяў Calendula патрабуецца дазвол на запіс</string>
|
||||
<string name="event_edit_more_fields">Дадатковыя палі</string>
|
||||
<string name="event_edit_add">Дадаць</string>
|
||||
<string name="event_edit_add_reminder">Дадаць напамін</string>
|
||||
<string name="event_edit_remove_reminder">Выдаліць напамін</string>
|
||||
<string name="event_edit_attendees">Наведвальнікі</string>
|
||||
<string name="event_edit_add_guest">Дадаць наведвальніка</string>
|
||||
<string name="event_edit_add_guest_hint">Дадаць наведвальніка праз электронную пошту…</string>
|
||||
<string name="event_edit_add_guest_from_contacts">Дадаць з кантактаў</string>
|
||||
<string name="event_edit_location_from_contacts">Выбраць адрас з кантактаў</string>
|
||||
<string name="event_edit_remove_guest">Выдаліць наведвальніка</string>
|
||||
<string name="event_edit_attendee_required">Неабходны</string>
|
||||
<string name="event_edit_attendee_optional">Неабавязковы</string>
|
||||
<string name="event_edit_attendees_note_synced">Calendula не рассылае запрашэньні. Наведвальнікі могуць атрымаць электронныя лісты з вашага ўліковага запісу календара пры сынхранізацыі.</string>
|
||||
<string name="event_edit_attendees_note_local">Захавана на гэтай прыладзе. Ніхто не атрымаў абвесткі.</string>
|
||||
<string name="event_edit_reminder_custom">Карыстальніцкі</string>
|
||||
<string name="reminder_unit_minutes">хв.</string>
|
||||
<string name="reminder_unit_hours">гадз.</string>
|
||||
<string name="reminder_unit_days">сут.</string>
|
||||
<string name="reminder_unit_weeks">тыд.</string>
|
||||
<string name="event_edit_availability">Даступнасьць</string>
|
||||
<string name="event_edit_visibility">Бачнасьць</string>
|
||||
<string name="event_edit_timezone_device">Часавы пояс прылады</string>
|
||||
<string name="event_edit_timezone_device_summary">Сочыць за вашым месцазнаходжаньнем</string>
|
||||
<string name="event_edit_timezone_search">Пошук часавых паясоў</string>
|
||||
<string name="event_edit_timezone_recent">Апошнія</string>
|
||||
<string name="event_edit_timezone_all">Усе часавыя паясы</string>
|
||||
<string name="event_edit_timezone_none">Няма часавага поясу, які адпавядае «%1$s»</string>
|
||||
<string name="event_edit_timezone_local_time">%1$s паводле вашага часу</string>
|
||||
<string name="event_edit_color">Колер</string>
|
||||
<string name="event_edit_color_default">Колер календара</string>
|
||||
<string name="event_edit_color_custom">Карыстальніцкі колер</string>
|
||||
<string name="event_edit_color_reset">Скід</string>
|
||||
<string name="event_edit_color_unsupported">Недаступна для гэтага календара</string>
|
||||
<string name="event_edit_color_unsupported_hint">Для гэтага календара не апублікаваны набор колераў. Для падобных календароў у наладах можна задаць карыстальніцкія колеры.</string>
|
||||
<string name="event_edit_color_sync_warning">Пры наступнай сынхранізацыі гэты каляндар можа страціць або перазапісаць колер.</string>
|
||||
<string name="event_edit_conflict_title">Падзея зьмянілася ў іншым месцы</string>
|
||||
<string name="event_edit_conflict_body">Падчас рэдагаваньня вамі гэтая падзея была зьменена ў выніку сынхранізацыі або іншай праграмай. Што адбудзецца з вашымі зьменамі?</string>
|
||||
<string name="event_edit_conflict_overwrite">Захаваць мае змены</string>
|
||||
<string name="event_edit_conflict_overwrite_hint">Толькі адрэдагаваныя вамі палі перазапішуць зьнешнія зьмены</string>
|
||||
<string name="event_edit_conflict_discard">Адкінуць мае зьмены</string>
|
||||
<string name="event_edit_conflict_discard_hint">Падзея застаецца такой, якой яна ёсьць зараз</string>
|
||||
<string name="event_edit_gone_title">Падзея выдалена</string>
|
||||
<string name="event_edit_gone_body">Гэтая падзея ўжо выдаленая, напрыклад на іншай прыладзе. Вашыя зьмены болей немагчыма захаваць.</string>
|
||||
<string name="import_reminder_prompt_title">Ужыць напамін па змаўчаньні?</string>
|
||||
<string name="import_reminder_prompt_body_none">Гэтая падзея была імпартаваная без напаміну.</string>
|
||||
<plurals name="import_reminder_prompt_body_existing">
|
||||
<item quantity="one">"Гэтая падзея была імпартаваная з наст. колькасьцю напамінаў: %1$d."</item>
|
||||
<item quantity="few">"Гэтая падзея была імпартаваная з наст. колькасьцю напамінаў: %1$d."</item>
|
||||
<item quantity="many">"Гэтая падзея была імпартаваная з наст. колькасьцю напамінаў: %1$d."</item>
|
||||
<item quantity="other">"Гэтая падзея была імпартаваная з наст. колькасьцю напамінаў: %1$d."</item>
|
||||
</plurals>
|
||||
<string name="import_reminder_prompt_apply">Ужыць змаўчаньні</string>
|
||||
<string name="import_reminder_prompt_keep">Пакінуць як ёсьць</string>
|
||||
<string name="event_edit_recurrence_none">Не паўтараецца</string>
|
||||
<string name="event_edit_recurrence_custom">Карыстальніцкі варыянт</string>
|
||||
<string name="event_edit_recurrence_every">Кожн.</string>
|
||||
<string name="recurrence_unit_days">сут.</string>
|
||||
<string name="recurrence_unit_weeks">тыд.</string>
|
||||
<string name="recurrence_unit_months">мес.</string>
|
||||
<string name="recurrence_unit_years">год/гады</string>
|
||||
<string name="event_edit_recurrence_incomplete">Увядзеце лік ад 1 да 999</string>
|
||||
<string name="event_edit_recurrence_next">Наступ.: %1$s</string>
|
||||
<string name="event_edit_recurrence_next_none">Гэтае правіла ніколі не паўтараецца</string>
|
||||
<string name="event_edit_recurrence_ends">Заканчэньне</string>
|
||||
<string name="event_edit_recurrence_end_never">Ніколі</string>
|
||||
<string name="event_edit_recurrence_end_until">На дату</string>
|
||||
<string name="event_edit_recurrence_end_count">Пасьля некалькіх разоў</string>
|
||||
<string name="event_edit_recurrence_times">разы(оў)</string>
|
||||
</resources>
|
||||
@@ -133,4 +133,47 @@
|
||||
<string name="import_reminder_prompt_apply">Применить стандартное</string>
|
||||
<string name="import_reminder_prompt_keep">Оставить как есть</string>
|
||||
<string name="event_edit_recurrence_none">Не повторяется</string>
|
||||
<string name="event_edit_recurrence_custom">Свой</string>
|
||||
<string name="event_edit_recurrence_every">Каждый</string>
|
||||
<string name="recurrence_unit_days">дни</string>
|
||||
<string name="recurrence_unit_weeks">недели</string>
|
||||
<string name="recurrence_unit_months">месяцы</string>
|
||||
<string name="recurrence_unit_years">года</string>
|
||||
<string name="event_edit_recurrence_incomplete">Введите число от 1 до 999</string>
|
||||
<string name="event_edit_recurrence_next_none">Это правило никогда не повторяется</string>
|
||||
<string name="event_edit_recurrence_ends">Заканчивается</string>
|
||||
<string name="event_edit_recurrence_end_never">Никогда</string>
|
||||
<string name="event_edit_recurrence_end_until">До</string>
|
||||
<string name="event_edit_recurrence_end_count">После определённого количества</string>
|
||||
<string name="event_availability_busy">Занят</string>
|
||||
<string name="event_access_default">Стандартный</string>
|
||||
<string name="event_access_public">Публичный</string>
|
||||
<string name="event_detail_all_day">Весь день</string>
|
||||
<string name="event_detail_calendar">Календарь</string>
|
||||
<string name="event_detail_calendar_unknown">Неизвестный календарь</string>
|
||||
<string name="event_detail_location">Местоположение</string>
|
||||
<string name="event_detail_description">Описание</string>
|
||||
<string name="event_detail_recurring">Повторяющееся событие</string>
|
||||
<string name="recurrence_daily">Каждый день</string>
|
||||
<string name="recurrence_weekly">Каждую неделю</string>
|
||||
<string name="recurrence_monthly">Каждый месяц</string>
|
||||
<string name="recurrence_yearly">Каждый год</string>
|
||||
<string name="recurrence_every_n_days">Каждые %1$d дней</string>
|
||||
<string name="recurrence_every_n_weeks">Каждые %1$d недель</string>
|
||||
<string name="recurrence_every_n_months">Каждые %1$d месяцев</string>
|
||||
<string name="recurrence_every_n_years">Каждые %1$d лет</string>
|
||||
<string name="event_detail_not_found">Это событие больше не существует.</string>
|
||||
<string name="event_attendee_accepted">Принято</string>
|
||||
<string name="event_attendee_declined">Отклонено</string>
|
||||
<string name="event_attendee_needs_action">Без ответа</string>
|
||||
<string name="event_detail_reminders">Напоминания</string>
|
||||
<string name="event_detail_timezone">Временная зона</string>
|
||||
<string name="event_status_cancelled">Отменён</string>
|
||||
<string name="event_availability_free">Бесплатно</string>
|
||||
<string name="event_access_private">Приватно</string>
|
||||
<string name="event_access_confidential">Конфиденциально</string>
|
||||
<string name="event_access_public_summary">Любой с доступом может видеть все детали</string>
|
||||
<string name="event_access_private_summary">Другие видят только, что вы заняты</string>
|
||||
<string name="event_detail_self_response">Ваш ответ: %1$s</string>
|
||||
<string name="reminder_at_time">Во время начала события</string>
|
||||
</resources>
|
||||
|
||||
190
app/src/main/res/values-sk/strings.xml
Normal file
190
app/src/main/res/values-sk/strings.xml
Normal file
@@ -0,0 +1,190 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="backup_interval_units">
|
||||
<item>Minutes</item>
|
||||
<item>Hours</item>
|
||||
<item>Days</item>
|
||||
<item>Weeks</item>
|
||||
</string-array>
|
||||
<string name="app_name">Calendula</string>
|
||||
<string name="app_tagline">Moderný kalendár.</string>
|
||||
<string name="state_loading">Načítava sa…</string>
|
||||
<string name="state_retry">Opakovať</string>
|
||||
<string name="state_failure_unknown">Niečo sa pokazilo.</string>
|
||||
<string name="state_failure_permission">Je potrebný prístup ku kalendáru.</string>
|
||||
<string name="state_failure_permission_action">Udeliť prístup</string>
|
||||
<string name="state_failure_no_calendars">Nie sú nakonfigurované žiadne kalendáre.</string>
|
||||
<string name="state_failure_no_calendars_action">Otvoriť systémové nastavenie kalendára</string>
|
||||
<string name="state_failure_provider">Nepodarilo sa načítať kalendár.</string>
|
||||
<string name="permission_rationale_title">Prehľadávajte všetky svoje udalosti v krásnom prevedení</string>
|
||||
<string name="permission_rationale_body">Calendula potrebuje prístup k vášmu kalendáru, aby mohla zobraziť a spravovať vaše udalosti.</string>
|
||||
<string name="permission_request_button">Udeliť prístup k kalendáru</string>
|
||||
<string name="permission_denied_title">Prístup ku kalendáru bol zamietnutý</string>
|
||||
<string name="permission_denied_body">Calendula nemôže zobraziť udalosti bez prístupu ku kalendáru. Môžete ho znova udeliť v nastavení systému.</string>
|
||||
<string name="permission_open_settings_button">Otvoriť systémové nastavenia</string>
|
||||
<string name="permission_retry_button">Skúsiť znova</string>
|
||||
<string name="permission_benefit_private_title">Súkromné už od začiatku</string>
|
||||
<string name="permission_benefit_private_body">Žiadny prístup k internetu, žiadna telemetria - nikdy nič z vášho telefónu neunikne.</string>
|
||||
<string name="permission_benefit_sync_title">Všetky vaše kalendáre pohromade</string>
|
||||
<string name="permission_benefit_sync_body">Google, CalDAV, lokálne kalendáre - čokoľvek synchronizované zo zariadením sa proste objaví.</string>
|
||||
<string name="permission_benefit_privacy_title">Nikdy žiadne sledovanie</string>
|
||||
<string name="permission_benefit_privacy_body">Žiadna telemetria, žiadne analytické nástroje, bez reklám.</string>
|
||||
<string name="permission_privacy_footnote">Ostáva na vašom zariadení · bez prístupu k internetu</string>
|
||||
<string name="month_prev">Predchádzajúci mesiac</string>
|
||||
<string name="month_next">Nasledujúci mesiac</string>
|
||||
<string name="month_today_action">Dnes</string>
|
||||
<string name="month_more_actions">Ďalšie možnosti</string>
|
||||
<string name="month_open_menu">Otvoriť menu</string>
|
||||
<string name="month_action_settings">Nastavenia</string>
|
||||
<string name="month_a11y_today_prefix">Dnes</string>
|
||||
<string name="week_today_action">Tento víkend</string>
|
||||
<string name="week_number_label">týžd.</string>
|
||||
<string name="day_today_action">Dnes</string>
|
||||
<string name="event_detail_back">Späť</string>
|
||||
<string name="event_detail_edit">Upraviť</string>
|
||||
<string name="event_detail_delete">Vymazať</string>
|
||||
<string name="event_detail_share">Zdielať</string>
|
||||
<string name="event_detail_duplicate">Duplikovať</string>
|
||||
<string name="event_share_chooser_title">Zdielať udalosť</string>
|
||||
<string name="event_share_failed">Túto udalosť nieje možné zdieľať.</string>
|
||||
<string name="event_delete_title">Odstrániť udalosť?</string>
|
||||
<string name="event_delete_body">Udalosť je odstránená z vášho kalendára aj zo všetkých synchronizovaných zariadení.</string>
|
||||
<string name="event_delete_recurring_title">Odstrániť opakujúcu sa udalosť</string>
|
||||
<string name="event_delete_option_occurrence">Iba túto udalosť</string>
|
||||
<string name="event_delete_option_following">Túto aj všetky nasledujúce udalosti</string>
|
||||
<string name="event_delete_option_series">Všetky udalosti v sérii</string>
|
||||
<string name="event_edit_recurring_title">Upraviť opakujúcu sa udalosť</string>
|
||||
<string name="event_delete_failed">Túto udalosť nebolo možné odstrániť</string>
|
||||
<string name="event_delete_write_denied">Calendula potrebuje povolenie k zápisu, aby mohla odstrániť udalosti</string>
|
||||
<string name="event_move_action">Presunúť…</string>
|
||||
<string name="event_move_recurring_title">Presunúť opakujúcu sa udalosť</string>
|
||||
<string name="event_move_occurrence_only">Presunútie celej série by sa zmenilo poradie dňou, na ktorú séria pripadá, takže presunúť je možné iba túto udalosť.</string>
|
||||
<string name="event_move_done">Presunuté na %1$s</string>
|
||||
<string name="event_move_undo">Vrátiť späť</string>
|
||||
<string name="event_move_undone">Navrátené naspäť</string>
|
||||
<string name="event_move_failed">Nemožno presunúť udalosť</string>
|
||||
<string name="event_move_write_denied">Calendula potrebuje povolenie k zápisu, aby mohla presúvať udalosti</string>
|
||||
<string name="event_move_gone">Táto udalosť už neexistuje</string>
|
||||
<string name="event_move_blocked_series_end">Nie je možné presunúť udalosť za koniec danej série</string>
|
||||
<string name="dialog_cancel">Zrušiť</string>
|
||||
<string name="dialog_ok">OK</string>
|
||||
<string name="event_edit_new_title">Nová udalosť</string>
|
||||
<string name="event_edit_close">Zavrieť</string>
|
||||
<string name="event_edit_save">Uložiť</string>
|
||||
<string name="event_edit_title_hint">Pridať názov</string>
|
||||
<string name="event_edit_managed_hint">Spravové kalendárom “%1$s” — názov, dátum a opakovanie je synchronizované z vašich kontaktov. Pripomienky, poloha a poznámky môžte upraviť.</string>
|
||||
<string name="event_edit_starts">Začiatok</string>
|
||||
<string name="event_edit_ends">Koniec</string>
|
||||
<string name="event_edit_error_end_before_start">Končí pred začiatkom</string>
|
||||
<string name="event_edit_error_no_calendar">Není dostupný kalendár, ktorý je možno upravovať</string>
|
||||
<string name="event_edit_save_failed">Nemožno udalosť uložiť</string>
|
||||
<string name="event_edit_write_denied">Calendula potrebuje povolenie k zápisu, aby mohla vytvárať udalosti</string>
|
||||
<string name="event_edit_more_fields">Viac poli</string>
|
||||
<string name="event_edit_add">Pridať</string>
|
||||
<string name="event_edit_add_reminder">Pridať pripomienku</string>
|
||||
<string name="event_edit_remove_reminder">Odstrániť pripomienku</string>
|
||||
<string name="event_edit_attendees">Hostia</string>
|
||||
<string name="event_edit_add_guest">Pridať hosťa</string>
|
||||
<string name="event_edit_add_guest_hint">Pridať hosťa e-mailom…</string>
|
||||
<string name="event_edit_add_guest_from_contacts">Pridať z kontaktov</string>
|
||||
<string name="event_edit_location_from_contacts">Vybrať adresu z kontaktov</string>
|
||||
<string name="event_edit_remove_guest">Odstrániť hosťa</string>
|
||||
<string name="event_edit_attendee_required">Nutné</string>
|
||||
<string name="event_edit_attendee_optional">Voliteľné</string>
|
||||
<string name="event_edit_attendees_note_synced">Calendula neposiela pozvánky. Váš kalendárny účet ich môže poslať keď sa synchronizuje.</string>
|
||||
<string name="event_edit_attendees_note_local">Uložené na tomto zariadení. Nikomu nebude poslaná notifikácia.</string>
|
||||
<string name="event_edit_reminder_custom">Vlastné</string>
|
||||
<string name="reminder_unit_minutes">minúty</string>
|
||||
<string name="reminder_unit_hours">hodiny</string>
|
||||
<string name="reminder_unit_days">dni</string>
|
||||
<string name="reminder_unit_weeks">týždne</string>
|
||||
<string name="event_edit_availability">Dostupnosť</string>
|
||||
<string name="event_edit_visibility">Viditeľnosť</string>
|
||||
<string name="event_edit_timezone_device">Časové pásmo zariadenia</string>
|
||||
<string name="event_edit_timezone_device_summary">Sleduje vás, nech ste kdekoľvek</string>
|
||||
<string name="event_edit_timezone_search">Vyhľadať časové pásmo</string>
|
||||
<string name="event_edit_timezone_recent">Nedávne</string>
|
||||
<string name="event_edit_timezone_all">Všetky časové pásma</string>
|
||||
<string name="event_edit_timezone_none">Žiadne časové pásmo nezodpovedá “%1$s”</string>
|
||||
<string name="event_edit_timezone_local_time">%1$s vášho času</string>
|
||||
<string name="event_edit_color">Farba</string>
|
||||
<string name="event_edit_color_default">Farba kalendáru</string>
|
||||
<string name="event_edit_color_custom">Vlastná farba</string>
|
||||
<string name="event_edit_color_reset">Obnoviť</string>
|
||||
<string name="event_edit_color_unsupported">Není k dispozícii pre tento kalendár</string>
|
||||
<string name="event_edit_color_unsupported_hint">Tento kalendár nemá vlastnú sadu farieb. Pre takýto kalendár môžte povoliť vlastné farby v nastaveniach.</string>
|
||||
<string name="event_edit_color_sync_warning">Tento kalendár môže vymazať alebo prepísať farbu pri ďalšej synchronizácii.</string>
|
||||
<string name="event_edit_conflict_title">Udalosť bola zmenená niekde inde</string>
|
||||
<string name="event_edit_conflict_body">Počas úprav sa táto udalosť zmenila — synchronizáciou alebo inou aplikáciou. Čo sa má stať s vašimi zmenami?</string>
|
||||
<string name="event_edit_conflict_overwrite">Uložiť zmeny</string>
|
||||
<string name="event_edit_conflict_overwrite_hint">Iba u upravených polí sa prepíše zmena z iného zdroja</string>
|
||||
<string name="event_edit_conflict_discard">Zrušiť zmeny</string>
|
||||
<string name="event_edit_conflict_discard_hint">Udalosť ostáva bez zmeny</string>
|
||||
<string name="event_edit_gone_title">Udalosť odstránená</string>
|
||||
<string name="event_edit_gone_body">Počas úprav bola táto udalosť odstránená, napríklad na inom zariadení. Vaše zmeny už nie je možné uložiť.</string>
|
||||
<string name="import_reminder_prompt_title">Chcete použiť predvolenú pripomienku?</string>
|
||||
<string name="import_reminder_prompt_body_none">Táto udalosť bola importovaná bez pripomienky.</string>
|
||||
<plurals name="import_reminder_prompt_body_existing">
|
||||
<item quantity="one">Táto udalosť bola importovaná s %1$d pripomienkou.</item>
|
||||
<item quantity="few">Táto udalosť bola importovaná s %1$d pripomienkami.</item>
|
||||
<item quantity="many">Táto udalosť bola importovaná s %1$d pripomienkami.</item>
|
||||
<item quantity="other">Táto udalosť bola importovaná s %1$d pripomienkami.</item>
|
||||
</plurals>
|
||||
<string name="import_reminder_prompt_apply">Nastaviť predvolené</string>
|
||||
<string name="import_reminder_prompt_keep">Nechať bez zmeny</string>
|
||||
<string name="event_edit_recurrence_none">Neopakuje sa</string>
|
||||
<string name="event_edit_recurrence_custom">Vlastné</string>
|
||||
<string name="event_edit_recurrence_every">Každý</string>
|
||||
<string name="recurrence_unit_days">dni</string>
|
||||
<string name="recurrence_unit_weeks">týždne</string>
|
||||
<string name="recurrence_unit_months">mesiace</string>
|
||||
<string name="recurrence_unit_years">roky</string>
|
||||
<string name="event_edit_recurrence_incomplete">Zadajte číslo od 1 do 999</string>
|
||||
<string name="event_edit_recurrence_next">Nasledujúce: %1$s</string>
|
||||
<string name="event_edit_recurrence_next_none">Toto pravidlo sa nikdy neopakuje</string>
|
||||
<string name="event_edit_recurrence_ends">Koniec</string>
|
||||
<string name="event_edit_recurrence_end_never">Nikdy</string>
|
||||
<string name="event_edit_recurrence_end_until">K určitému dátumu</string>
|
||||
<string name="event_edit_recurrence_end_count">Po určitom počtu opakovaní</string>
|
||||
<string name="event_edit_recurrence_times">opakovaní</string>
|
||||
<string name="event_edit_error_recurrence_ends_before_start">Opakovanie končí pred začiatkom udalosti</string>
|
||||
<string name="event_availability_busy">Zaneprázdnený</string>
|
||||
<string name="event_access_default">Predvolený</string>
|
||||
<string name="event_access_public">Verejný</string>
|
||||
<string name="event_detail_all_day">Celý deň</string>
|
||||
<string name="event_detail_calendar">Kalendár</string>
|
||||
<string name="event_detail_calendar_unknown">Neznámy kalendár</string>
|
||||
<string name="event_detail_location">Poloha</string>
|
||||
<string name="event_detail_description">Popis</string>
|
||||
<string name="event_detail_attendees">Účastníci</string>
|
||||
<string name="event_detail_recurrence">Opakovanie</string>
|
||||
<string name="event_detail_recurring">Opakovaná udalosť</string>
|
||||
<string name="recurrence_daily">Každý deň</string>
|
||||
<string name="recurrence_weekly">Každý týždeň</string>
|
||||
<string name="recurrence_monthly">Každý mesiac</string>
|
||||
<string name="recurrence_yearly">Každý rok</string>
|
||||
<string name="recurrence_every_n_days">Každý %1$d. deň</string>
|
||||
<string name="recurrence_every_n_weeks">Každý %1$d. týždeň</string>
|
||||
<string name="recurrence_every_n_months">Každý %1$d. mesiac</string>
|
||||
<string name="recurrence_every_n_years">Každý %1$d. rok</string>
|
||||
<string name="recurrence_on_days">%1$s v %2$s</string>
|
||||
<string name="recurrence_with_until">%1$s do%2$s</string>
|
||||
<string name="recurrence_with_count">%1$s, %2$d opakovaní</string>
|
||||
<string name="event_detail_not_found">Táto udalosť už neexistuje.</string>
|
||||
<string name="event_attendee_accepted">Akceptované</string>
|
||||
<string name="event_attendee_declined">Zamietnuté</string>
|
||||
<string name="event_attendee_tentative">Predbežne</string>
|
||||
<string name="event_attendee_needs_action">Bez odozvy</string>
|
||||
<string name="event_attendee_unknown">—</string>
|
||||
<string name="event_detail_reminders">Pripomienky</string>
|
||||
<string name="event_detail_timezone">Časové pásmo</string>
|
||||
<string name="event_status_tentative">Predbežne</string>
|
||||
<string name="event_status_cancelled">Zrušené</string>
|
||||
<string name="event_availability_free">Voľno</string>
|
||||
<string name="event_access_private">Súkromné</string>
|
||||
<string name="event_access_confidential">Tajné</string>
|
||||
<string name="event_access_default_summary">Bežná činnosť kalendáru</string>
|
||||
<string name="event_access_public_summary">Kdekoľvek s prístupom vidí všetky detaily</string>
|
||||
<string name="event_access_private_summary">Ostatný vidia iba že ste zaneprázdnený</string>
|
||||
<string name="event_access_confidential_summary">Označené ako utajené; záleží na kalendárnom účtu čo toto znamená</string>
|
||||
</resources>
|
||||
@@ -558,6 +558,183 @@ class EventWriteMapperTest {
|
||||
assertThat(values[CalendarContract.Events.ALL_DAY]).isEqualTo(1)
|
||||
}
|
||||
|
||||
// --- toDetachedOccurrence ("edit only this event", no _sync_id) ---
|
||||
|
||||
@Test
|
||||
fun `a detached occurrence drops the series rule and becomes a one-off row`() {
|
||||
val edited = form().copy(title = "Moved", rrule = "FREQ=WEEKLY;BYDAY=TH")
|
||||
val detached = edited.toDetachedOccurrence()
|
||||
|
||||
val values = buildEventInsertValues(
|
||||
form = detached,
|
||||
uid = "uid@calendula",
|
||||
times = detached.toWriteTimes(berlin),
|
||||
)
|
||||
assertThat(values[CalendarContract.Events.TITLE]).isEqualTo("Moved")
|
||||
// The rule must not survive: without a _sync_id nothing clears an
|
||||
// inherited RRULE the way the exception path's DTSTART + DURATION does,
|
||||
// so keeping it would insert a second *series* overlapping the first
|
||||
// (Codeberg #234's stray duplicate).
|
||||
assertThat(values).doesNotContainKey(CalendarContract.Events.RRULE)
|
||||
assertThat(values).doesNotContainKey(CalendarContract.Events.DURATION)
|
||||
// A one-off row carries DTEND — the invariant buildEventInsertValues
|
||||
// holds for every non-recurring event.
|
||||
assertThat(values[CalendarContract.Events.DTSTART]).isEqualTo(1_781_164_800_000L)
|
||||
assertThat(values[CalendarContract.Events.DTEND]).isEqualTo(1_781_170_200_000L)
|
||||
assertThat(values[CalendarContract.Events.EVENT_TIMEZONE]).isEqualTo("Europe/Berlin")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a detached occurrence carries every edited field onto the new row`() {
|
||||
// The detached row is built from the form rather than cloned from the
|
||||
// parent, so anything the user edited has to survive the trip — a field
|
||||
// dropped here is an edit silently lost.
|
||||
val edited = form(timezone = "America/New_York").copy(
|
||||
title = " Standup ",
|
||||
location = "Room 2",
|
||||
description = "notes",
|
||||
reminders = listOf(10),
|
||||
availability = Availability.Free,
|
||||
accessLevel = AccessLevel.Private,
|
||||
rrule = "FREQ=DAILY",
|
||||
)
|
||||
val detached = edited.toDetachedOccurrence()
|
||||
|
||||
val values = buildEventInsertValues(
|
||||
form = detached,
|
||||
uid = "uid@calendula",
|
||||
times = detached.toWriteTimes(berlin),
|
||||
)
|
||||
assertThat(values[CalendarContract.Events.TITLE]).isEqualTo("Standup")
|
||||
assertThat(values[CalendarContract.Events.EVENT_LOCATION]).isEqualTo("Room 2")
|
||||
assertThat(values[CalendarContract.Events.DESCRIPTION]).isEqualTo("notes")
|
||||
assertThat(values[CalendarContract.Events.AVAILABILITY])
|
||||
.isEqualTo(CalendarContract.Events.AVAILABILITY_FREE)
|
||||
assertThat(values[CalendarContract.Events.ACCESS_LEVEL])
|
||||
.isEqualTo(CalendarContract.Events.ACCESS_PRIVATE)
|
||||
// The pinned zone survives too — a detached occurrence must not be
|
||||
// silently re-anchored to the device.
|
||||
assertThat(values[CalendarContract.Events.EVENT_TIMEZONE]).isEqualTo("America/New_York")
|
||||
assertThat(values[CalendarContract.Events.UID_2445]).isEqualTo("uid@calendula")
|
||||
// Reminders and guests aren't columns — the insert path seeds them from
|
||||
// the form, so they only have to survive on the form itself.
|
||||
assertThat(detached.reminders).containsExactly(10)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a detached all-day occurrence stays on UTC midnights`() {
|
||||
val edited = form(
|
||||
isAllDay = true,
|
||||
start = LocalDateTime(LocalDate(2026, 6, 11), LocalTime(0, 0)),
|
||||
end = LocalDateTime(LocalDate(2026, 6, 11), LocalTime(0, 0)),
|
||||
).copy(title = "Birthday", rrule = "FREQ=YEARLY")
|
||||
val detached = edited.toDetachedOccurrence()
|
||||
|
||||
val values = buildEventInsertValues(
|
||||
form = detached,
|
||||
uid = "uid@calendula",
|
||||
times = detached.toWriteTimes(berlin),
|
||||
)
|
||||
assertThat(values[CalendarContract.Events.ALL_DAY]).isEqualTo(1)
|
||||
assertThat(values[CalendarContract.Events.EVENT_TIMEZONE]).isEqualTo("UTC")
|
||||
assertThat(values[CalendarContract.Events.DTSTART]).isEqualTo(1_781_136_000_000L)
|
||||
// Exclusive DTEND — the next UTC midnight.
|
||||
assertThat(values[CalendarContract.Events.DTEND]).isEqualTo(1_781_222_400_000L)
|
||||
assertThat(values).doesNotContainKey(CalendarContract.Events.RRULE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the detached row lands exactly where the parent's exdate removes it`() {
|
||||
// The two halves of the no-_sync_id edit have to agree, or the user sees
|
||||
// the occurrence twice or not at all. With the time left untouched, the
|
||||
// EXDATE stamp and the detached row's DTSTART describe the same instant.
|
||||
val edited = form().copy(title = "Renamed", rrule = "FREQ=WEEKLY")
|
||||
val occurrenceMillis = 1_781_164_800_000L
|
||||
|
||||
val parent = buildOccurrenceExdateValues(
|
||||
existingExdate = null,
|
||||
occurrenceMillis = occurrenceMillis,
|
||||
dtStartMillis = 1_780_560_000_000L,
|
||||
rrule = "FREQ=WEEKLY",
|
||||
duration = "P5400S",
|
||||
timezone = "Europe/Berlin",
|
||||
allDay = 0,
|
||||
)
|
||||
val detached = edited.toDetachedOccurrence()
|
||||
val inserted = buildEventInsertValues(
|
||||
form = detached,
|
||||
uid = "uid@calendula",
|
||||
times = detached.toWriteTimes(berlin),
|
||||
)
|
||||
assertThat(parent[CalendarContract.Events.EXDATE]).isEqualTo("20260611T080000Z")
|
||||
assertThat(inserted[CalendarContract.Events.DTSTART]).isEqualTo(occurrenceMillis)
|
||||
// The parent keeps its own anchor and rule — only this occurrence leaves.
|
||||
assertThat(parent[CalendarContract.Events.DTSTART]).isEqualTo(1_780_560_000_000L)
|
||||
assertThat(parent[CalendarContract.Events.RRULE]).isEqualTo("FREQ=WEEKLY")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a detached all-day occurrence matches its date-only exdate stamp`() {
|
||||
val parent = buildOccurrenceExdateValues(
|
||||
existingExdate = null,
|
||||
occurrenceMillis = 1_781_136_000_000L, // 2026-06-11T00:00:00Z
|
||||
dtStartMillis = 1_749_600_000_000L,
|
||||
rrule = "FREQ=YEARLY",
|
||||
duration = "P1D",
|
||||
timezone = "UTC",
|
||||
allDay = 1,
|
||||
)
|
||||
val detached = form(
|
||||
isAllDay = true,
|
||||
start = LocalDateTime(LocalDate(2026, 6, 11), LocalTime(0, 0)),
|
||||
end = LocalDateTime(LocalDate(2026, 6, 11), LocalTime(0, 0)),
|
||||
).copy(rrule = "FREQ=YEARLY").toDetachedOccurrence()
|
||||
val inserted = buildEventInsertValues(
|
||||
form = detached,
|
||||
uid = "uid@calendula",
|
||||
times = detached.toWriteTimes(berlin),
|
||||
)
|
||||
assertThat(parent[CalendarContract.Events.EXDATE]).isEqualTo("20260611")
|
||||
assertThat(inserted[CalendarContract.Events.DTSTART]).isEqualTo(1_781_136_000_000L)
|
||||
}
|
||||
|
||||
// --- exdateContains (guards a second detach of the same occurrence) ---
|
||||
|
||||
@Test
|
||||
fun `an occurrence already excluded is recognised, timed and all-day`() {
|
||||
// Detaching twice would fold the repeated EXDATE away and still report a
|
||||
// changed row, leaving a second standalone copy of one occurrence.
|
||||
assertThat(
|
||||
exdateContains("20260611T080000Z", 1_781_164_800_000L, isAllDay = false),
|
||||
).isTrue()
|
||||
assertThat(
|
||||
exdateContains("20260611", 1_781_136_000_000L, isAllDay = true),
|
||||
).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an occurrence not in the exdate list is not mistaken for an excluded one`() {
|
||||
assertThat(exdateContains(null, 1_781_164_800_000L, isAllDay = false)).isFalse()
|
||||
assertThat(exdateContains("", 1_781_164_800_000L, isAllDay = false)).isFalse()
|
||||
// A neighbouring occurrence must not match — the guard is per-instant.
|
||||
assertThat(
|
||||
exdateContains("20260610T080000Z", 1_781_164_800_000L, isAllDay = false),
|
||||
).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an exclusion is found anywhere in a multi-entry exdate list`() {
|
||||
// Whitespace after a comma is legal in the stored column and must not
|
||||
// hide an exclusion — that would let a duplicate through.
|
||||
assertThat(
|
||||
exdateContains(
|
||||
"20260604T080000Z, 20260611T080000Z,20260618T080000Z",
|
||||
1_781_164_800_000L,
|
||||
isAllDay = false,
|
||||
),
|
||||
).isTrue()
|
||||
}
|
||||
|
||||
// --- per-event colour ---
|
||||
|
||||
@Test
|
||||
|
||||
@@ -172,9 +172,12 @@ F-Droid but not Play:
|
||||
Until those are fixed, Play's graphics are managed by hand in the console. Then
|
||||
pass `images:true` to the `listing` lane.
|
||||
|
||||
**Track.** Uploads go to `internal` by default; promotion to production stays a
|
||||
manual action in the Play Console, matching the rule that UI releases wait for
|
||||
on-device review. Override with the `PLAY_TRACK` repo variable.
|
||||
**Track.** Uploads go straight to `production` at a full rollout
|
||||
(`PLAY_RELEASE_STATUS=completed`). The merge to `main` is already the human
|
||||
gate — a release only gets there after on-device review — so a second manual
|
||||
promotion in the Play Console added delay without adding a decision. Set the
|
||||
`PLAY_TRACK` repo variable (e.g. `internal`) to stage a release instead, or
|
||||
`PLAY_RELEASE_STATUS=draft` to hold it unpublished.
|
||||
|
||||
### Manual re-sign / recovery
|
||||
|
||||
@@ -233,7 +236,7 @@ Two consequences worth remembering:
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `PLAY_TRACK` | `internal` | Play track the bundle is uploaded to. |
|
||||
| `PLAY_TRACK` | `production` | Play track the bundle is uploaded to. Set to `internal`/`alpha`/`beta` to stage instead. |
|
||||
| `PLAY_RELEASE_STATUS` | `completed` | `completed`, `draft`, `inProgress` or `halted`. |
|
||||
| `PLAY_DRY_RUN` | `false` | `true` validates the Play edit against the API and discards it — use to rehearse. |
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ platform :android do
|
||||
|
||||
supply(
|
||||
aab: aab,
|
||||
track: options[:track] || "internal",
|
||||
track: options[:track] || "production",
|
||||
release_status: options[:release_status] || "completed",
|
||||
|
||||
# The APK belongs to F-Droid and the Codeberg download; Play only ever
|
||||
|
||||
@@ -5,16 +5,16 @@ ksp = "2.3.11"
|
||||
hilt = "2.60.1"
|
||||
coreKtx = "1.19.0"
|
||||
appcompat = "1.7.1"
|
||||
lifecycleRuntime = "2.10.0"
|
||||
lifecycleRuntime = "2.11.0"
|
||||
activityCompose = "1.13.0"
|
||||
composeBom = "2026.06.01"
|
||||
# Material 3 Expressive APIs currently live only in the 1.5 alpha line.
|
||||
# Pin explicitly to override the BOM (which ships stable 1.4.0).
|
||||
# Re-evaluate when 1.5.0 stable lands.
|
||||
material3 = "1.5.0-alpha25"
|
||||
material3 = "1.5.0-alpha26"
|
||||
datastore = "1.2.1"
|
||||
junit = "6.1.2"
|
||||
junitPlatform = "6.1.2"
|
||||
junit = "6.1.3"
|
||||
junitPlatform = "6.1.3"
|
||||
truth = "1.4.5"
|
||||
androidxJunit = "1.3.0"
|
||||
espressoCore = "3.7.0"
|
||||
|
||||
Reference in New Issue
Block a user