From 2f497b2b95a4bb60b85ad315695eba0b21aa03d7 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Wed, 23 Sep 2026 21:55:57 +0200 Subject: [PATCH] Localise the unnamed-calendar placeholder (#329) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CalendarMapper substituted a hardcoded German "(Unbenannter Kalender)" for a null CALENDAR_DISPLAY_NAME, so it showed in every locale wherever calendars are listed — and the calendar editor prefills from that name, so saving wrote the literal into the provider for real. Leave the name blank and add a calendar_unnamed string the views substitute. The write paths now store the trimmed name as given instead of inventing one: a stored placeholder would freeze the creation-time language into the provider, and the editor already refuses a blank name. --- .../calendula/data/calendar/CalendarDataSource.kt | 6 +++--- .../calendula/data/calendar/CalendarMapper.kt | 5 +++-- .../calendula/data/calendar/Projections.kt | 4 ---- .../calendula/ui/calendars/BackupScreen.kt | 2 +- .../calendula/ui/calendars/CalendarsScreen.kt | 8 ++++---- .../calendula/ui/common/AccountGroups.kt | 10 ++++++---- .../calendula/ui/common/CalendarPickerGroups.kt | 2 +- .../calendula/ui/detail/EventDetailScreen.kt | 3 ++- .../calendula/ui/edit/EventEditScreen.kt | 5 ++++- .../calendula/ui/filter/CalendarFilterList.kt | 7 ++++--- .../calendula/ui/onboarding/HiddenCalendarsStep.kt | 4 ++-- .../calendula/ui/settings/EventFormSettings.kt | 2 +- .../calendula/ui/settings/NotificationSettings.kt | 4 ++-- app/src/main/res/values/strings.xml | 3 +++ .../calendula/data/calendar/CalendarMapperTest.kt | 4 ++-- 15 files changed, 38 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/data/calendar/CalendarDataSource.kt b/app/src/main/java/de/jeanlucmakiola/calendula/data/calendar/CalendarDataSource.kt index 9417d96..fd3283d 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/data/calendar/CalendarDataSource.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/data/calendar/CalendarDataSource.kt @@ -376,7 +376,7 @@ class AndroidCalendarDataSource @Inject constructor( .build() override fun createLocalCalendar(displayName: String, color: Int, description: String?): Long { - val name = displayName.trim().ifEmpty { Fallbacks.UNNAMED_CALENDAR } + val name = displayName.trim() val values = ContentValues().apply { put(CalendarContract.Calendars.ACCOUNT_NAME, LOCAL_ACCOUNT_NAME) put(CalendarContract.Calendars.ACCOUNT_TYPE, CalendarContract.ACCOUNT_TYPE_LOCAL) @@ -400,7 +400,7 @@ class AndroidCalendarDataSource @Inject constructor( } override fun updateCalendar(id: Long, displayName: String, color: Int, description: String?) { - val name = displayName.trim().ifEmpty { Fallbacks.UNNAMED_CALENDAR } + val name = displayName.trim() val values = ContentValues().apply { put(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, name) put(CalendarContract.Calendars.NAME, name) @@ -464,7 +464,7 @@ class AndroidCalendarDataSource @Inject constructor( PackageManager.PERMISSION_GRANTED override fun createManagedCalendar(displayName: String, color: Int, type: SpecialDateType): Long { - val name = displayName.trim().ifEmpty { Fallbacks.UNNAMED_CALENDAR } + val name = displayName.trim() val values = ContentValues().apply { put(CalendarContract.Calendars.ACCOUNT_NAME, LOCAL_ACCOUNT_NAME) put(CalendarContract.Calendars.ACCOUNT_TYPE, CalendarContract.ACCOUNT_TYPE_LOCAL) diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/data/calendar/CalendarMapper.kt b/app/src/main/java/de/jeanlucmakiola/calendula/data/calendar/CalendarMapper.kt index edfbd4a..fb95227 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/data/calendar/CalendarMapper.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/data/calendar/CalendarMapper.kt @@ -8,8 +8,9 @@ internal fun ColumnReader.toCalendarSource(): CalendarSource { val isLocal = accountType == CalendarContract.ACCOUNT_TYPE_LOCAL return CalendarSource( id = getLong(CalendarProjection.IDX_ID), - displayName = getString(CalendarProjection.IDX_DISPLAY_NAME) - ?: Fallbacks.UNNAMED_CALENDAR, + // Blank when the provider row has no name: the placeholder is a display + // string and belongs to the UI, where it can be localised (#329). + displayName = getString(CalendarProjection.IDX_DISPLAY_NAME).orEmpty(), accountName = getString(CalendarProjection.IDX_ACCOUNT_NAME).orEmpty(), accountType = accountType, color = getInt(CalendarProjection.IDX_COLOR), diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/data/calendar/Projections.kt b/app/src/main/java/de/jeanlucmakiola/calendula/data/calendar/Projections.kt index c107d8f..e9e5a46 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/data/calendar/Projections.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/data/calendar/Projections.kt @@ -312,7 +312,3 @@ internal object ReminderProjection { const val IDX_MINUTES = 0 const val IDX_METHOD = 1 } - -internal object Fallbacks { - const val UNNAMED_CALENDAR = "(Unbenannter Kalender)" -} diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/calendars/BackupScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/calendars/BackupScreen.kt index 6fc13fc..7a3af49 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/calendars/BackupScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/calendars/BackupScreen.kt @@ -312,7 +312,7 @@ private fun ExportCalendarPicker( calendars.forEachIndexed { index, calendar -> val isSelected = calendar.id in selected GroupedRow( - title = calendar.displayName, + title = calendar.displayName.ifBlank { stringResource(R.string.calendar_unnamed) }, summary = calendar.description, position = positionOf(index, calendars.size), leading = { CalendarColorChip(calendar.color) }, diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/calendars/CalendarsScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/calendars/CalendarsScreen.kt index 58e1075..7d00c20 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/calendars/CalendarsScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/calendars/CalendarsScreen.kt @@ -246,7 +246,7 @@ private fun CalendarsList( local.forEachIndexed { index, calendar -> val disabled = !calendar.isVisibleInSystem GroupedRow( - title = calendar.displayName, + title = calendar.displayName.ifBlank { stringResource(R.string.calendar_unnamed) }, summary = calendarRowSummary(calendar), position = if (index == local.lastIndex) Position.Bottom else Position.Middle, container = MaterialTheme.colorScheme.surfaceContainerHighest, @@ -254,7 +254,7 @@ private fun CalendarsList( leading = { CalendarColorChip(calendar.color, dimIf(disabled)) }, trailing = { EnableSwitch( - calendarName = calendar.displayName, + calendarName = calendar.displayName.ifBlank { stringResource(R.string.calendar_unnamed) }, enabled = !disabled, onToggle = { enabled -> onSetVisible(calendar.id, enabled) }, ) @@ -331,7 +331,7 @@ private fun CalendarsList( ordered.forEachIndexed { index, calendar -> val disabled = !calendar.isVisibleInSystem || calendar.isNotSynced GroupedRow( - title = calendar.displayName, + title = calendar.displayName.ifBlank { stringResource(R.string.calendar_unnamed) }, summary = calendarRowSummary(calendar), position = if (index == ordered.lastIndex) Position.Bottom else Position.Middle, container = MaterialTheme.colorScheme.surfaceContainerHighest, @@ -340,7 +340,7 @@ private fun CalendarsList( trailing = if (calendar.hasVisibilitySwitch) { { EnableSwitch( - calendarName = calendar.displayName, + calendarName = calendar.displayName.ifBlank { stringResource(R.string.calendar_unnamed) }, enabled = calendar.isVisibleInSystem, onToggle = { enabled -> onSetVisible(calendar.id, enabled) diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/AccountGroups.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/AccountGroups.kt index 5d01a7d..7ec3dfd 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/AccountGroups.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/AccountGroups.kt @@ -56,12 +56,14 @@ fun List.groupByAccount(): List { * account comes from when another account shares the name (#77). */ @Composable -fun accountGroupTitle(group: CalendarAccountGroup): String = - if (!group.ambiguous) { - group.label +fun accountGroupTitle(group: CalendarAccountGroup): String { + val label = group.label.ifBlank { stringResource(R.string.calendar_unnamed) } + return if (!group.ambiguous) { + label } else { - stringResource(R.string.calendars_account_from_source, group.label, sourceAppName(group.accountType)) + stringResource(R.string.calendars_account_from_source, label, sourceAppName(group.accountType)) } +} /** * The human name of the app backing [accountType], falling back to the raw diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/CalendarPickerGroups.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/CalendarPickerGroups.kt index 61c073e..802bbb8 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/CalendarPickerGroups.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/CalendarPickerGroups.kt @@ -116,7 +116,7 @@ private fun CalendarPickerGroup( calendars.forEachIndexed { index, calendar -> val isSelected = calendar.id == selectedId GroupedRow( - title = calendar.displayName, + title = calendar.displayName.ifBlank { stringResource(R.string.calendar_unnamed) }, position = if (index == calendars.lastIndex) Position.Bottom else Position.Middle, selected = isSelected, leading = { CalendarColorChip(calendar.color) }, diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/detail/EventDetailScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/detail/EventDetailScreen.kt index c183e61..2360edd 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/detail/EventDetailScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/detail/EventDetailScreen.kt @@ -527,7 +527,8 @@ private fun EventDetailContent( iconContentDescription = stringResource(R.string.event_detail_calendar), ) { Text( - text = state.calendarName ?: stringResource(R.string.event_detail_calendar_unknown), + text = state.calendarName?.ifBlank { stringResource(R.string.calendar_unnamed) } + ?: stringResource(R.string.event_detail_calendar_unknown), style = MaterialTheme.typography.titleMedium, ) } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt index 6b9336d..f591797 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt @@ -620,7 +620,9 @@ private fun EventEditContent( Text( text = stringResource( R.string.event_edit_managed_hint, - selectedCalendar?.displayName.orEmpty(), + selectedCalendar?.displayName + ?.ifBlank { stringResource(R.string.calendar_unnamed) } + .orEmpty(), ), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, @@ -719,6 +721,7 @@ private fun EventEditContent( ) { Text( text = selectedCalendar?.displayName + ?.ifBlank { stringResource(R.string.calendar_unnamed) } ?: stringResource(R.string.event_edit_error_no_calendar), style = MaterialTheme.typography.titleMedium, color = if (selectedCalendar == null) { diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/filter/CalendarFilterList.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/filter/CalendarFilterList.kt index 7bf8480..9b37431 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/filter/CalendarFilterList.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/filter/CalendarFilterList.kt @@ -62,15 +62,16 @@ private fun FilterList( ) { Column(modifier = modifier.fillMaxWidth()) { groups.forEach { group -> + val account = group.account.ifBlank { stringResource(R.string.calendar_unnamed) } Text( text = if (group.ambiguous) { stringResource( R.string.calendars_account_from_source, - group.account, + account, sourceAppName(group.accountType), ) } else { - group.account + account }, style = MaterialTheme.typography.labelMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, @@ -83,7 +84,7 @@ private fun FilterList( ) group.calendars.forEachIndexed { index, cal -> GroupedRow( - title = cal.displayName, + title = cal.displayName.ifBlank { stringResource(R.string.calendar_unnamed) }, position = positionOf(index, group.calendars.size), minHeight = 56.dp, leading = { CalendarColorChip(cal.color) }, diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/HiddenCalendarsStep.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/HiddenCalendarsStep.kt index d364d0d..472d336 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/HiddenCalendarsStep.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/HiddenCalendarsStep.kt @@ -129,7 +129,7 @@ internal fun HiddenCalendarsStep( calendars.forEachIndexed { index, calendar -> val off = !calendar.isVisibleInSystem GroupedRow( - title = calendar.displayName, + title = calendar.displayName.ifBlank { stringResource(R.string.calendar_unnamed) }, summary = calendar.accountName.takeIf { it.isNotBlank() }, position = when { calendars.size == 1 -> Position.Alone @@ -141,7 +141,7 @@ internal fun HiddenCalendarsStep( trailing = if (calendar.hasVisibilitySwitch) { { CalendarSwitch( - calendarName = calendar.displayName, + calendarName = calendar.displayName.ifBlank { stringResource(R.string.calendar_unnamed) }, enabled = !off, onToggle = { on -> onSetVisible(calendar.id, on) }, ) diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/EventFormSettings.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/EventFormSettings.kt index 61ffe0a..a9674aa 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/EventFormSettings.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/EventFormSettings.kt @@ -143,7 +143,7 @@ internal fun EventFormScreen( durationCalendars.forEachIndexed { index, calendar -> val override = state.perCalendarEventDuration[calendar.id] GroupedRow( - title = calendar.displayName, + title = calendar.displayName.ifBlank { stringResource(R.string.calendar_unnamed) }, summary = override?.let { durationLabel(it) } ?: stringResource( R.string.settings_calendar_duration_inherits, diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/NotificationSettings.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/NotificationSettings.kt index 0b62f48..cc77805 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/NotificationSettings.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/NotificationSettings.kt @@ -181,7 +181,7 @@ internal fun NotificationsScreen( // own section — link there instead. if (calendar.id in state.managedCalendarIds) { GroupedRow( - title = calendar.displayName, + title = calendar.displayName.ifBlank { stringResource(R.string.calendar_unnamed) }, summary = stringResource(R.string.settings_calendar_reminders_managed_hint), position = Position.Alone, leading = { CalendarColorChip(calendar.color) }, @@ -198,7 +198,7 @@ internal fun NotificationsScreen( } val expanded = calendar.id in expandedCalendars GroupedRow( - title = calendar.displayName, + title = calendar.displayName.ifBlank { stringResource(R.string.calendar_unnamed) }, position = if (expanded) Position.Top else Position.Alone, leading = { CalendarColorChip(calendar.color) }, trailing = { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 73cc474..8ef8b99 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -290,6 +290,9 @@ (No title) + + (No name) + Step %1$d of %2$d Your events live only here diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/data/calendar/CalendarMapperTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/data/calendar/CalendarMapperTest.kt index 1645692..2507f9f 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/data/calendar/CalendarMapperTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/data/calendar/CalendarMapperTest.kt @@ -64,9 +64,9 @@ class CalendarMapperTest { } @Test - fun `null displayName falls back to placeholder`() { + fun `null displayName is left blank for the UI to fill in`() { val src = reader(displayName = null).toCalendarSource() - assertThat(src.displayName).isEqualTo(Fallbacks.UNNAMED_CALENDAR) + assertThat(src.displayName).isEmpty() } @Test