Skip to content

Commit

Permalink
feat: make app translatable
Browse files Browse the repository at this point in the history
  • Loading branch information
JanMalch committed Dec 16, 2023
1 parent 7892fb6 commit 718eea1
Show file tree
Hide file tree
Showing 17 changed files with 295 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.janmalch.woroboro.MainActivity
import io.github.janmalch.woroboro.R
import io.github.janmalch.woroboro.models.Reminder
import io.github.janmalch.woroboro.models.RoutineFilter
import javax.inject.Inject
Expand Down Expand Up @@ -47,7 +48,7 @@ class AndroidReminderNotifications @Inject constructor(
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(android.R.drawable.ic_lock_idle_alarm)
.setContentTitle(reminder.name)
.setContentText("Es ist Zeit für eine deiner Routinen!") // TODO: i18n
.setContentText(context.getString(R.string.reminder_notification_content))
.setLargeIcon(image)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setCategory(Notification.CATEGORY_REMINDER)
Expand All @@ -70,9 +71,9 @@ class AndroidReminderNotifications @Inject constructor(
}

private fun createNotificationChannel() {
val name = "Woroboro Reminders" // TODO: i18n
val name = context.getString(R.string.reminders)
val descriptionText =
"Benachrichtigungen für deine selbsterstellten Erinnerungen." // TODO: i18n
context.getString(R.string.reminders_notification_channel_description)
val importance = NotificationManager.IMPORTANCE_DEFAULT
val channel = NotificationChannel(CHANNEL_ID, name, importance).apply {
description = descriptionText
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.navigation.NavHostController
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import io.github.janmalch.woroboro.R
import io.github.janmalch.woroboro.ui.exercise.EXERCISES_GRAPH_ROUTE
import io.github.janmalch.woroboro.ui.exercise.navigateToExercisesGraph
import io.github.janmalch.woroboro.ui.reminder.REMINDER_GRAPH_ROUTE
Expand Down Expand Up @@ -91,15 +93,15 @@ fun AppBottomBar(navController: NavHostController) {
navController.navigateToRoutineGraph(forBackstack(navController))
},
icon = { Icon(Icons.Outlined.FitnessCenter, contentDescription = null) },
label = { Text(text = "Routinen") },
label = { Text(text = stringResource(id = R.string.routines)) },
)
NavigationBarItem(
selected = isExerciseTabActive,
onClick = {
navController.navigateToExercisesGraph(forBackstack(navController))
},
icon = { Icon(Icons.Outlined.SportsGymnastics, contentDescription = null) },
label = { Text(text = "Übungen") },
label = { Text(text = stringResource(id = R.string.exercises)) },
)
NavigationBarItem(
selected = isRemindersTabActive,
Expand All @@ -113,7 +115,7 @@ fun AppBottomBar(navController: NavHostController) {
Icon(Icons.Outlined.Notifications, contentDescription = null)
}
},
label = { Text(text = "Erinnerungen") },
label = { Text(text = stringResource(id = R.string.reminders)) },
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,17 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import io.github.janmalch.woroboro.R
import io.github.janmalch.woroboro.utils.findWholeUnit
import java.time.LocalTime
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoUnit
import java.util.Locale
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlin.time.DurationUnit
Expand Down Expand Up @@ -99,7 +100,7 @@ fun TimeField(
onValueChange(LocalTime.of(state.hour, state.minute))
isPickerOpen = false
}) {
Text(text = "OK")
Text(text = stringResource(R.string.confirm))
}
},
text = {
Expand Down Expand Up @@ -251,13 +252,27 @@ fun DurationTextField(
@Composable
@ReadOnlyComposable
fun abbreviationOfDurationUnit(unit: DurationUnit): String {
return unit.name.lowercase().capitalize(Locale.ROOT).take(3).plus('.') // TODO: translate
return stringResource(
id = when (unit) {
DurationUnit.SECONDS -> R.string.unit_seconds_abbreviation
DurationUnit.MINUTES -> R.string.unit_minutes_abbreviation
DurationUnit.HOURS -> R.string.unit_hours_abbreviation
else -> R.string.unit_unknown_abbreviation
}
)
}

@Composable
@ReadOnlyComposable
fun nameOfDurationUnit(unit: DurationUnit): String {
return unit.name.lowercase().capitalize(Locale.ROOT) // TODO: translate
return stringResource(
id = when (unit) {
DurationUnit.SECONDS -> R.string.unit_seconds_name
DurationUnit.MINUTES -> R.string.unit_minutes_name
DurationUnit.HOURS -> R.string.unit_hours_name
else -> R.string.unit_unknown_name
}
)
}

private fun Long.coerceToInt(): Int = coerceAtMost(Int.MAX_VALUE.toLong()).toInt()
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import io.github.janmalch.woroboro.R
import io.github.janmalch.woroboro.models.Exercise
import io.github.janmalch.woroboro.models.ExerciseExecution
import io.github.janmalch.woroboro.models.Media
Expand Down Expand Up @@ -126,19 +128,23 @@ fun ExerciseListItem(
fun exerciseExecution(
execution: ExerciseExecution,
): String {
// TODO: translate
val base = when {
execution.reps != null -> "${execution.sets} × ${execution.reps}"
execution.reps != null -> stringResource(
R.string.exercise_execution_sets_reps,
execution.sets,
execution.reps
)

execution.hold != null -> {
val hold = formatDuration(execution.hold)
"${execution.sets} × $hold"
stringResource(R.string.exercise_execution_sets_hold, execution.sets, hold)
}

else -> "${execution.sets}"
else -> stringResource(R.string.exercise_execution_sets, execution.sets)
}
return if (execution.pause != null) {
val pause = formatDuration(execution.pause)
"$base · $pause Pause"
stringResource(R.string.exercise_execution_with_pause, base, pause)
} else {
base
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package io.github.janmalch.woroboro.ui.components.common

import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.res.stringResource
import io.github.janmalch.woroboro.R
import kotlin.time.Duration

@Composable
Expand All @@ -19,36 +21,24 @@ fun formatDuration(duration: Duration, zero: String = seconds(0)): String {

@Composable
@ReadOnlyComposable
fun seconds(value: Long): String {
return "${value}s" // TODO: translate
}
fun seconds(value: Long): String = stringResource(id = R.string.duration_in_seconds, value)

@Composable
@ReadOnlyComposable
fun seconds(value: Int): String {
return "${value}s" // TODO: translate
}
fun seconds(value: Int): String = stringResource(id = R.string.duration_in_seconds, value)

@Composable
@ReadOnlyComposable
fun minutes(value: Long): String {
return "${value}m" // TODO: translate
}
fun minutes(value: Long): String = stringResource(id = R.string.duration_in_minutes, value)

@Composable
@ReadOnlyComposable
fun minutes(value: Int): String {
return "${value}m" // TODO: translate
}
fun minutes(value: Int): String = stringResource(id = R.string.duration_in_minutes, value)

@Composable
@ReadOnlyComposable
fun hours(value: Long): String {
return "${value}h" // TODO: translate
}
fun hours(value: Long): String = stringResource(id = R.string.duration_in_hours, value)

@Composable
@ReadOnlyComposable
fun hours(value: Int): String {
return "${value}h" // TODO: translate
}
fun hours(value: Int): String = stringResource(id = R.string.duration_in_hours, value)
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import io.github.janmalch.woroboro.R
import io.github.janmalch.woroboro.models.Exercise
import io.github.janmalch.woroboro.models.Tag
import io.github.janmalch.woroboro.ui.components.ExerciseListItem
Expand Down Expand Up @@ -68,14 +70,14 @@ fun ExerciseListScreen(
Scaffold(
topBar = {
SearchTopAppBar(
title = { Text("Übungen") },
title = { Text(text = stringResource(id = R.string.exercises)) },
query = textQuery,
placeholder = "Nach Übungen suchen…",
placeholder = stringResource(R.string.exercise_search_placeholder),
onQueryChange = onTextQueryChange,
actions = {
MoreMenu {
MoreMenuItem(
text = { Text(text = "Tags bearbeiten") },
text = { Text(text = stringResource(R.string.edit_tags)) },
icon = {
Icon(Icons.Rounded.Edit, contentDescription = null)
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,11 @@ import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import io.github.janmalch.woroboro.R
import io.github.janmalch.woroboro.models.EditedExercise
import io.github.janmalch.woroboro.models.EditedMedia
import io.github.janmalch.woroboro.models.Exercise
Expand Down Expand Up @@ -169,11 +171,11 @@ fun ExerciseEditorScreen(
)
) {
ButtonLoading(isVisible = isLoading)
Text(text = "Speichern")
Text(text = stringResource(R.string.save))
}
MoreMenu(enabled = !isLoading && exercise != null) {
MoreMenuItem(
text = { Text(text = "Zu Routine hinzufügen") },
text = { Text(text = stringResource(R.string.add_to_routine)) },
icon = {
Icon(
Icons.Rounded.PostAdd,
Expand All @@ -183,7 +185,7 @@ fun ExerciseEditorScreen(
onClick = { if (exercise != null) isAddToRoutineDialogOpen = true }
)
MoreMenuItem(
text = { Text(text = "Übung löschen") },
text = { Text(text = stringResource(R.string.delete_exercise)) },
icon = {
Icon(
Icons.Rounded.DeleteOutline,
Expand Down Expand Up @@ -214,7 +216,7 @@ fun ExerciseEditorScreen(
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text(text = "Name") },
label = { Text(text = stringResource(R.string.name)) },
singleLine = true,
// isError = name.isBlank(), // TODO: only when dirty/touched?
modifier = Modifier.fillMaxWidth(),
Expand All @@ -227,7 +229,7 @@ fun ExerciseEditorScreen(
OutlinedTextField(
value = description,
onValueChange = { description = it },
label = { Text(text = "Beschreibung") },
label = { Text(text = stringResource(R.string.description)) },
leadingIcon = { Icon(Icons.Rounded.QuestionMark, contentDescription = null) },
singleLine = false,
modifier = Modifier
Expand All @@ -247,15 +249,27 @@ fun ExerciseEditorScreen(
value = sets,
onValueChange = { sets = it },
required = true,
label = { Text(text = "Sätze", softWrap = false, maxLines = 1) },
label = {
Text(
text = stringResource(R.string.sets),
softWrap = false,
maxLines = 1
)
},
leadingIcon = { Icon(Icons.Rounded.Replay, contentDescription = null) },
modifier = Modifier.weight(1F),
)
DurationTextField(
value = pause,
onValueChange = { pause = it },
required = false,
label = { Text(text = "Pause", softWrap = false, maxLines = 1) },
label = {
Text(
text = stringResource(R.string.pause),
softWrap = false,
maxLines = 1
)
},
leadingIcon = {
Icon(
Icons.Rounded.PauseCircle,
Expand All @@ -277,15 +291,27 @@ fun ExerciseEditorScreen(
value = reps,
onValueChange = { reps = it },
required = false,
label = { Text(text = "Wdh.", softWrap = false, maxLines = 1) },
label = {
Text(
text = stringResource(R.string.reps),
softWrap = false,
maxLines = 1
)
},
leadingIcon = { Icon(Icons.Rounded.Repeat, contentDescription = null) },
modifier = Modifier.weight(1F),
)
DurationTextField(
value = hold,
onValueChange = { hold = it },
required = false,
label = { Text(text = "Halten", softWrap = false, maxLines = 1) },
label = {
Text(
text = stringResource(R.string.hold),
softWrap = false,
maxLines = 1
)
},
leadingIcon = {
Icon(
Icons.Rounded.HourglassBottom,
Expand All @@ -303,7 +329,7 @@ fun ExerciseEditorScreen(
MediaPicker(
value = media,
onValueChange = { media = it },
title = { Text(text = "Bilder und Videos") },
title = { Text(text = stringResource(R.string.images_and_videos)) },
contentPadding = PaddingValues(horizontal = 24.dp),
modifier = Modifier
.fillMaxWidth()
Expand All @@ -320,7 +346,7 @@ fun ExerciseEditorScreen(
) {

IsFavoriteCheckbox(
text = "Lieblingsübung",
text = stringResource(R.string.favorite_exercise),
value = isFavorite,
onValueChange = { isFavorite = it },
)
Expand Down Expand Up @@ -385,7 +411,7 @@ fun AddToRoutineDialog(
value = filter,
onValueChange = { filter = it },
singleLine = true,
label = { Text("Nach Routine suchen…") },
label = { Text(stringResource(R.string.routine_search_placeholder)) },
)
Spacer(modifier = Modifier.height(16.dp))
LazyColumn {
Expand Down
Loading

0 comments on commit 718eea1

Please sign in to comment.