Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a3b684744b | |||
| d11cc82fec | |||
| ef9ed4f5fc | |||
| d7d389ba8a | |||
| 091248f30f | |||
| 7cdd88b11a | |||
| abc98c8fa8 | |||
| c52ce4d497 | |||
| 09120e9d9d | |||
| db65db6232 | |||
| 6058013951 | |||
| 7c892d2bb6 | |||
| 90a1d98322 | |||
| 05cc4311a7 | |||
| f364ab05dc | |||
| 0b6f26da99 | |||
| 83c3428b05 | |||
| f4c2780e34 | |||
| d26714f043 | |||
| 3a5e33af93 | |||
| bd27f32caa | |||
| 9c9e6009c7 | |||
| 498e6b8064 | |||
| 5aa31153e3 | |||
| 23f4d70ac1 | |||
| fdcf82757d | |||
| 3890dd6f52 | |||
| d5041492a9 | |||
| ec5d93efab |
@@ -121,6 +121,19 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
|
<!-- Per-widget residence picker (gitea#6). Each widget provider
|
||||||
|
XML declares `android:configure` pointing at this activity,
|
||||||
|
so the system launches it whenever the user pins a new
|
||||||
|
tile or hits "Edit Widget" on an existing one. -->
|
||||||
|
<activity
|
||||||
|
android:name=".widget.WidgetConfigActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:theme="@android:style/Theme.Material.Light.NoActionBar">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
<!-- Small Widget Receiver (2x1) -->
|
<!-- Small Widget Receiver (2x1) -->
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".widget.HoneyDueSmallWidgetReceiver"
|
android:name=".widget.HoneyDueSmallWidgetReceiver"
|
||||||
|
|||||||
@@ -314,9 +314,10 @@ class MainActivity : FragmentActivity(), SingletonImageLoader.Factory {
|
|||||||
return ImageLoader.Builder(context)
|
return ImageLoader.Builder(context)
|
||||||
.components {
|
.components {
|
||||||
// Auth interceptor runs before the network fetcher so every
|
// Auth interceptor runs before the network fetcher so every
|
||||||
// image request carries the current Authorization header, with
|
// image request carries the current X-Session-Token header
|
||||||
// 401 -> refresh-token -> retry handled transparently. Mirrors
|
// (Kratos session token), with 401 -> session re-check ->
|
||||||
// iOS AuthenticatedImage.swift (Stream U).
|
// retry handled transparently. Mirrors iOS
|
||||||
|
// AuthenticatedImage.swift.
|
||||||
add(
|
add(
|
||||||
CoilAuthInterceptor(
|
CoilAuthInterceptor(
|
||||||
tokenProvider = { TokenStorage.getToken() },
|
tokenProvider = { TokenStorage.getToken() },
|
||||||
@@ -324,7 +325,6 @@ class MainActivity : FragmentActivity(), SingletonImageLoader.Factory {
|
|||||||
val r = APILayer.refreshToken()
|
val r = APILayer.refreshToken()
|
||||||
if (r is ApiResult.Success) r.data else null
|
if (r is ApiResult.Success) r.data else null
|
||||||
},
|
},
|
||||||
authScheme = "Token",
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
add(KtorNetworkFetcherFactory())
|
add(KtorNetworkFetcherFactory())
|
||||||
|
|||||||
@@ -45,8 +45,14 @@ class HoneyDueLargeWidget : GlanceAppWidget() {
|
|||||||
|
|
||||||
override suspend fun provideGlance(context: Context, id: GlanceId) {
|
override suspend fun provideGlance(context: Context, id: GlanceId) {
|
||||||
val repo = WidgetDataRepository.get(context)
|
val repo = WidgetDataRepository.get(context)
|
||||||
val tasks = repo.loadTasks()
|
// Per-instance residence scoping (gitea#6). Stats are computed
|
||||||
val stats = repo.computeStats()
|
// off the same filtered list so the bottom-tile counters
|
||||||
|
// ("Overdue / 7 days / 30 days") match the visible tasks
|
||||||
|
// instead of aggregating across every residence.
|
||||||
|
val appWidgetId =
|
||||||
|
androidx.glance.appwidget.GlanceAppWidgetManager(context).getAppWidgetId(id)
|
||||||
|
val tasks = repo.loadTasksForWidget(appWidgetId)
|
||||||
|
val stats = repo.computeStatsFromTasks(tasks)
|
||||||
val tier = repo.loadTierState()
|
val tier = repo.loadTierState()
|
||||||
val isPremium = tier.equals("premium", ignoreCase = true)
|
val isPremium = tier.equals("premium", ignoreCase = true)
|
||||||
|
|
||||||
@@ -135,4 +141,9 @@ class HoneyDueLargeWidget : GlanceAppWidget() {
|
|||||||
/** AppWidget receiver for the large widget. */
|
/** AppWidget receiver for the large widget. */
|
||||||
class HoneyDueLargeWidgetReceiver : GlanceAppWidgetReceiver() {
|
class HoneyDueLargeWidgetReceiver : GlanceAppWidgetReceiver() {
|
||||||
override val glanceAppWidget: GlanceAppWidget = HoneyDueLargeWidget()
|
override val glanceAppWidget: GlanceAppWidget = HoneyDueLargeWidget()
|
||||||
|
|
||||||
|
override fun onDeleted(context: Context, appWidgetIds: IntArray) {
|
||||||
|
super.onDeleted(context, appWidgetIds)
|
||||||
|
WidgetReceiverHelpers.purgeResidenceScopes(context, appWidgetIds)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,10 @@ class HoneyDueMediumWidget : GlanceAppWidget() {
|
|||||||
|
|
||||||
override suspend fun provideGlance(context: Context, id: GlanceId) {
|
override suspend fun provideGlance(context: Context, id: GlanceId) {
|
||||||
val repo = WidgetDataRepository.get(context)
|
val repo = WidgetDataRepository.get(context)
|
||||||
val tasks = repo.loadTasks()
|
// Per-instance residence scoping (gitea#6). See small widget for rationale.
|
||||||
|
val appWidgetId =
|
||||||
|
androidx.glance.appwidget.GlanceAppWidgetManager(context).getAppWidgetId(id)
|
||||||
|
val tasks = repo.loadTasksForWidget(appWidgetId)
|
||||||
val tier = repo.loadTierState()
|
val tier = repo.loadTierState()
|
||||||
val isPremium = tier.equals("premium", ignoreCase = true)
|
val isPremium = tier.equals("premium", ignoreCase = true)
|
||||||
|
|
||||||
@@ -122,4 +125,9 @@ class HoneyDueMediumWidget : GlanceAppWidget() {
|
|||||||
/** AppWidget receiver for the medium widget. */
|
/** AppWidget receiver for the medium widget. */
|
||||||
class HoneyDueMediumWidgetReceiver : GlanceAppWidgetReceiver() {
|
class HoneyDueMediumWidgetReceiver : GlanceAppWidgetReceiver() {
|
||||||
override val glanceAppWidget: GlanceAppWidget = HoneyDueMediumWidget()
|
override val glanceAppWidget: GlanceAppWidget = HoneyDueMediumWidget()
|
||||||
|
|
||||||
|
override fun onDeleted(context: Context, appWidgetIds: IntArray) {
|
||||||
|
super.onDeleted(context, appWidgetIds)
|
||||||
|
WidgetReceiverHelpers.purgeResidenceScopes(context, appWidgetIds)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.tt.honeyDue.widget
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.glance.GlanceId
|
import androidx.glance.GlanceId
|
||||||
@@ -43,7 +44,13 @@ class HoneyDueSmallWidget : GlanceAppWidget() {
|
|||||||
|
|
||||||
override suspend fun provideGlance(context: Context, id: GlanceId) {
|
override suspend fun provideGlance(context: Context, id: GlanceId) {
|
||||||
val repo = WidgetDataRepository.get(context)
|
val repo = WidgetDataRepository.get(context)
|
||||||
val tasks = repo.loadTasks()
|
// Resolve which residence this widget instance is scoped to
|
||||||
|
// (gitea#6). `loadTasksForWidget` falls back to "All residences"
|
||||||
|
// when no scope is saved, matching pre-#6 behaviour for tiles
|
||||||
|
// that haven't been configured yet.
|
||||||
|
val appWidgetId =
|
||||||
|
androidx.glance.appwidget.GlanceAppWidgetManager(context).getAppWidgetId(id)
|
||||||
|
val tasks = repo.loadTasksForWidget(appWidgetId)
|
||||||
val tier = repo.loadTierState()
|
val tier = repo.loadTierState()
|
||||||
val isPremium = tier.equals("premium", ignoreCase = true)
|
val isPremium = tier.equals("premium", ignoreCase = true)
|
||||||
|
|
||||||
@@ -125,4 +132,35 @@ class OpenAppAction : ActionCallback {
|
|||||||
/** AppWidget receiver for the small widget. */
|
/** AppWidget receiver for the small widget. */
|
||||||
class HoneyDueSmallWidgetReceiver : GlanceAppWidgetReceiver() {
|
class HoneyDueSmallWidgetReceiver : GlanceAppWidgetReceiver() {
|
||||||
override val glanceAppWidget: GlanceAppWidget = HoneyDueSmallWidget()
|
override val glanceAppWidget: GlanceAppWidget = HoneyDueSmallWidget()
|
||||||
|
|
||||||
|
override fun onDeleted(context: Context, appWidgetIds: IntArray) {
|
||||||
|
super.onDeleted(context, appWidgetIds)
|
||||||
|
// Clean per-instance residence scope when the user removes a tile
|
||||||
|
// so dangling `widget_residence_id_<n>` keys don't accumulate in
|
||||||
|
// the DataStore (gitea#6).
|
||||||
|
WidgetReceiverHelpers.purgeResidenceScopes(context, appWidgetIds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared helpers for honeyDue Glance widget receivers. Kept in a
|
||||||
|
* top-level utility so every receiver size (Small / Medium / Large)
|
||||||
|
* uses identical cleanup logic.
|
||||||
|
*/
|
||||||
|
internal object WidgetReceiverHelpers {
|
||||||
|
@OptIn(kotlinx.coroutines.DelicateCoroutinesApi::class)
|
||||||
|
fun purgeResidenceScopes(context: Context, appWidgetIds: IntArray) {
|
||||||
|
if (appWidgetIds.isEmpty()) return
|
||||||
|
val repo = WidgetDataRepository.get(context)
|
||||||
|
// Fire-and-forget on a background dispatcher — `onDeleted` runs
|
||||||
|
// on the broadcast thread which doesn't permit suspend calls.
|
||||||
|
// GlobalScope is correct here: the IO is short-lived (one
|
||||||
|
// DataStore edit per removed appWidgetId) and there's no
|
||||||
|
// coroutine-scope tied to a long-lived receiver to attach to.
|
||||||
|
kotlinx.coroutines.GlobalScope.launch(kotlinx.coroutines.Dispatchers.IO) {
|
||||||
|
for (id in appWidgetIds) {
|
||||||
|
repo.clearResidenceIdFor(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,270 @@
|
|||||||
|
package com.tt.honeyDue.widget
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.appwidget.AppWidgetManager
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Check
|
||||||
|
import androidx.compose.material.icons.filled.Home
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.glance.appwidget.GlanceAppWidgetManager
|
||||||
|
import androidx.glance.appwidget.updateAll
|
||||||
|
import com.tt.honeyDue.ui.theme.AppSpacing
|
||||||
|
import com.tt.honeyDue.ui.theme.HoneyDueTheme
|
||||||
|
import com.tt.honeyDue.ui.theme.ThemeManager
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Per-widget residence selector. Launched by the system when the user
|
||||||
|
* pins a new honeyDue widget (because each widget provider XML now
|
||||||
|
* declares `android:configure`) and again when they hit "Edit Widget".
|
||||||
|
*
|
||||||
|
* Saves the chosen residence id under
|
||||||
|
* `widget_residence_id_<appWidgetId>` in [WidgetDataStore] so each
|
||||||
|
* widget instance can independently scope its task list (gitea#6).
|
||||||
|
* Selecting "All residences" clears the key and the widget reverts to
|
||||||
|
* the legacy unscoped behaviour.
|
||||||
|
*/
|
||||||
|
class WidgetConfigActivity : ComponentActivity() {
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
// The system passes the just-created widget id in the extras.
|
||||||
|
// Without it we don't know which widget to configure — bail
|
||||||
|
// with CANCELED so the system removes the placeholder tile.
|
||||||
|
val appWidgetId = intent?.extras?.getInt(
|
||||||
|
AppWidgetManager.EXTRA_APPWIDGET_ID,
|
||||||
|
AppWidgetManager.INVALID_APPWIDGET_ID
|
||||||
|
) ?: AppWidgetManager.INVALID_APPWIDGET_ID
|
||||||
|
|
||||||
|
if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
|
||||||
|
setResult(Activity.RESULT_CANCELED)
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default a CANCEL result so the widget is removed if the user
|
||||||
|
// dismisses without saving (matches the Android convention).
|
||||||
|
setResult(
|
||||||
|
Activity.RESULT_CANCELED,
|
||||||
|
Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
|
||||||
|
)
|
||||||
|
|
||||||
|
setContent {
|
||||||
|
val theme = ThemeManager.currentTheme
|
||||||
|
HoneyDueTheme(themeColors = theme) {
|
||||||
|
WidgetConfigScreen(
|
||||||
|
appWidgetId = appWidgetId,
|
||||||
|
onCommit = { residenceId ->
|
||||||
|
lifecycleScope.launch {
|
||||||
|
val repo = WidgetDataRepository.get(this@WidgetConfigActivity)
|
||||||
|
repo.saveResidenceIdFor(appWidgetId, residenceId)
|
||||||
|
// Repaint every widget tile so this one
|
||||||
|
// picks up the new scope on the next frame
|
||||||
|
// (Glance handles which `appWidgetId` we
|
||||||
|
// belong to via the per-instance state).
|
||||||
|
HoneyDueSmallWidget().updateAll(this@WidgetConfigActivity)
|
||||||
|
HoneyDueMediumWidget().updateAll(this@WidgetConfigActivity)
|
||||||
|
HoneyDueLargeWidget().updateAll(this@WidgetConfigActivity)
|
||||||
|
|
||||||
|
setResult(
|
||||||
|
Activity.RESULT_OK,
|
||||||
|
Intent().putExtra(
|
||||||
|
AppWidgetManager.EXTRA_APPWIDGET_ID,
|
||||||
|
appWidgetId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The actual picker UI. Loads residences from [WidgetDataRepository]
|
||||||
|
* and offers an "All residences" option above them. Empty state shows
|
||||||
|
* a helper message instead of an empty list (user hasn't created any
|
||||||
|
* residences yet, or the main app hasn't synced).
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun WidgetConfigScreen(
|
||||||
|
appWidgetId: Int,
|
||||||
|
onCommit: (Long?) -> Unit
|
||||||
|
) {
|
||||||
|
var residences by remember { mutableStateOf<List<WidgetResidenceDto>?>(null) }
|
||||||
|
var selectedId by remember { mutableStateOf<Long?>(null) }
|
||||||
|
|
||||||
|
val context = androidx.compose.ui.platform.LocalContext.current
|
||||||
|
LaunchedEffect(appWidgetId) {
|
||||||
|
val repo = WidgetDataRepository.get(context)
|
||||||
|
// Pre-select whatever the user picked last time they configured
|
||||||
|
// this same widget; falls back to "All residences" on first run.
|
||||||
|
selectedId = repo.loadResidenceIdFor(appWidgetId)
|
||||||
|
residences = repo.loadResidences()
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(MaterialTheme.colorScheme.background)
|
||||||
|
.padding(AppSpacing.lg)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Choose a residence",
|
||||||
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
color = MaterialTheme.colorScheme.onBackground
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(AppSpacing.sm))
|
||||||
|
Text(
|
||||||
|
text = "This widget will only show tasks for the residence you pick. " +
|
||||||
|
"Choose \"All residences\" to keep showing every home.",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(AppSpacing.lg))
|
||||||
|
|
||||||
|
val items = residences
|
||||||
|
if (items == null) {
|
||||||
|
// Loading state — DataStore reads off the IO dispatcher.
|
||||||
|
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
|
CircularProgressIndicator()
|
||||||
|
}
|
||||||
|
return@Column
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.weight(1f, fill = true),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(AppSpacing.sm)
|
||||||
|
) {
|
||||||
|
// "All residences" — selecting clears the per-widget key.
|
||||||
|
item {
|
||||||
|
ResidenceRow(
|
||||||
|
title = "All residences",
|
||||||
|
isSelected = selectedId == null,
|
||||||
|
onClick = { selectedId = null }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (items.isEmpty()) {
|
||||||
|
item {
|
||||||
|
EmptyResidencesNote()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
items(items, key = { it.id }) { residence ->
|
||||||
|
ResidenceRow(
|
||||||
|
title = residence.name,
|
||||||
|
isSelected = selectedId == residence.id,
|
||||||
|
onClick = { selectedId = residence.id }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(Modifier.height(AppSpacing.lg))
|
||||||
|
Button(
|
||||||
|
onClick = { onCommit(selectedId) },
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text("Save")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ResidenceRow(
|
||||||
|
title: String,
|
||||||
|
isSelected: Boolean,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(RoundedCornerShape(AppSpacing.md))
|
||||||
|
.background(
|
||||||
|
if (isSelected) MaterialTheme.colorScheme.primaryContainer
|
||||||
|
else MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
)
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
.padding(AppSpacing.lg)
|
||||||
|
) {
|
||||||
|
androidx.compose.foundation.layout.Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Home,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = if (isSelected) MaterialTheme.colorScheme.primary
|
||||||
|
else MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
Spacer(Modifier.fillMaxWidth(0.04f))
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
if (isSelected) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Check,
|
||||||
|
contentDescription = "Selected",
|
||||||
|
tint = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun EmptyResidencesNote() {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(RoundedCornerShape(AppSpacing.md))
|
||||||
|
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||||
|
.padding(AppSpacing.lg)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "No residences yet — open honeyDue and add a property first, " +
|
||||||
|
"then come back to configure this widget.",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -115,6 +115,66 @@ class WidgetDataRepository internal constructor(private val context: Context) {
|
|||||||
return all.filterNot { it.id in pending }
|
return all.filterNot { it.id in pending }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the cached task list filtered by [residenceId]. Pass `null` to
|
||||||
|
* return every residence's tasks (the "All residences" widget option,
|
||||||
|
* matching pre-gitea#6 behaviour).
|
||||||
|
*
|
||||||
|
* Pending completions are excluded — same contract as [loadTasks].
|
||||||
|
*/
|
||||||
|
suspend fun loadTasksForResidence(residenceId: Long?): List<WidgetTaskDto> {
|
||||||
|
val all = loadTasks()
|
||||||
|
return filterTasksForResidence(all, residenceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the residence scope for [appWidgetId] and return only its
|
||||||
|
* tasks. The widget [GlanceAppWidget.provideGlance] looks up its
|
||||||
|
* `appWidgetId` and calls this; configuration changes take effect on
|
||||||
|
* the next `updateAll` invocation.
|
||||||
|
*/
|
||||||
|
suspend fun loadTasksForWidget(appWidgetId: Int): List<WidgetTaskDto> {
|
||||||
|
val residenceId = store.readResidenceIdFor(appWidgetId)
|
||||||
|
return loadTasksForResidence(residenceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Residence sidecar (gitea#6)
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persist the user's residences so [WidgetConfigActivity] can offer
|
||||||
|
* them in its picker. Called from the main app whenever
|
||||||
|
* `DataManager.myResidences` updates.
|
||||||
|
*/
|
||||||
|
suspend fun saveResidences(residences: List<WidgetResidenceDto>) {
|
||||||
|
store.writeResidencesJson(json.encodeToString(residences))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Read the persisted residence list (empty when never written or after logout). */
|
||||||
|
suspend fun loadResidences(): List<WidgetResidenceDto> {
|
||||||
|
val raw = store.readResidencesJson()
|
||||||
|
return try {
|
||||||
|
json.decodeFromString<List<WidgetResidenceDto>>(raw)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Read which residence this widget instance is currently scoped to (null = All). */
|
||||||
|
suspend fun loadResidenceIdFor(appWidgetId: Int): Long? =
|
||||||
|
store.readResidenceIdFor(appWidgetId)
|
||||||
|
|
||||||
|
/** Persist the chosen residence for this widget instance. */
|
||||||
|
suspend fun saveResidenceIdFor(appWidgetId: Int, residenceId: Long?) {
|
||||||
|
store.writeResidenceIdFor(appWidgetId, residenceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Drop the per-widget residence selection when the widget is removed. */
|
||||||
|
suspend fun clearResidenceIdFor(appWidgetId: Int) {
|
||||||
|
store.clearResidenceIdFor(appWidgetId)
|
||||||
|
}
|
||||||
|
|
||||||
/** Queue a task id for optimistic completion. See [loadTasks]. */
|
/** Queue a task id for optimistic completion. See [loadTasks]. */
|
||||||
suspend fun markPendingCompletion(taskId: Long) {
|
suspend fun markPendingCompletion(taskId: Long) {
|
||||||
val current = store.readPendingCompletionIds().toMutableSet()
|
val current = store.readPendingCompletionIds().toMutableSet()
|
||||||
@@ -141,8 +201,15 @@ class WidgetDataRepository internal constructor(private val context: Context) {
|
|||||||
*
|
*
|
||||||
* Pending-completion tasks are excluded (via [loadTasks]).
|
* Pending-completion tasks are excluded (via [loadTasks]).
|
||||||
*/
|
*/
|
||||||
suspend fun computeStats(): WidgetStats {
|
suspend fun computeStats(): WidgetStats = computeStatsFromTasks(loadTasks())
|
||||||
val tasks = loadTasks()
|
|
||||||
|
/**
|
||||||
|
* Compute the same stats off a pre-filtered task list. Used by
|
||||||
|
* [HoneyDueLargeWidget] after applying the per-widget residence
|
||||||
|
* scope (gitea#6) so the stat tiles reflect only the residence the
|
||||||
|
* user picked.
|
||||||
|
*/
|
||||||
|
fun computeStatsFromTasks(tasks: List<WidgetTaskDto>): WidgetStats {
|
||||||
var overdue = 0
|
var overdue = 0
|
||||||
var within7 = 0
|
var within7 = 0
|
||||||
var within8To30 = 0
|
var within8To30 = 0
|
||||||
@@ -257,5 +324,18 @@ class WidgetDataRepository internal constructor(private val context: Context) {
|
|||||||
|
|
||||||
/** Legacy accessor — delegates to [get]. */
|
/** Legacy accessor — delegates to [get]. */
|
||||||
fun getInstance(context: Context): WidgetDataRepository = get(context)
|
fun getInstance(context: Context): WidgetDataRepository = get(context)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pure filter — exposed for unit-test coverage. Mirrors iOS'
|
||||||
|
* `WidgetDataManager.filterTasks(_:forResidenceId:)` semantics
|
||||||
|
* (gitea#6).
|
||||||
|
*/
|
||||||
|
fun filterTasksForResidence(
|
||||||
|
tasks: List<WidgetTaskDto>,
|
||||||
|
residenceId: Long?
|
||||||
|
): List<WidgetTaskDto> {
|
||||||
|
if (residenceId == null) return tasks
|
||||||
|
return tasks.filter { it.residenceId == residenceId }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,15 @@ internal object WidgetDataStoreKeys {
|
|||||||
val PENDING_COMPLETION_IDS = stringPreferencesKey("pending_completion_ids")
|
val PENDING_COMPLETION_IDS = stringPreferencesKey("pending_completion_ids")
|
||||||
val LAST_REFRESH_TIME = longPreferencesKey("last_refresh_time")
|
val LAST_REFRESH_TIME = longPreferencesKey("last_refresh_time")
|
||||||
val USER_TIER = stringPreferencesKey("user_tier")
|
val USER_TIER = stringPreferencesKey("user_tier")
|
||||||
|
/** JSON-serialized List<WidgetResidenceDto> for the configuration picker (gitea#6). */
|
||||||
|
val WIDGET_RESIDENCES_JSON = stringPreferencesKey("widget_residences_json")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a key for the `Long` residence id this `appWidgetId` is
|
||||||
|
* scoped to. Missing key = "All residences" (legacy behaviour).
|
||||||
|
*/
|
||||||
|
fun residenceIdKeyFor(appWidgetId: Int) =
|
||||||
|
longPreferencesKey("widget_residence_id_$appWidgetId")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -90,6 +99,56 @@ class WidgetDataStore(private val context: Context) {
|
|||||||
prefs.remove(WidgetDataStoreKeys.PENDING_COMPLETION_IDS)
|
prefs.remove(WidgetDataStoreKeys.PENDING_COMPLETION_IDS)
|
||||||
prefs.remove(WidgetDataStoreKeys.LAST_REFRESH_TIME)
|
prefs.remove(WidgetDataStoreKeys.LAST_REFRESH_TIME)
|
||||||
prefs.remove(WidgetDataStoreKeys.USER_TIER)
|
prefs.remove(WidgetDataStoreKeys.USER_TIER)
|
||||||
|
prefs.remove(WidgetDataStoreKeys.WIDGET_RESIDENCES_JSON)
|
||||||
|
// Per-widget residence ids are added dynamically as
|
||||||
|
// `widget_residence_id_<appWidgetId>` keys; sweep them by
|
||||||
|
// prefix so logout doesn't leave dangling per-instance
|
||||||
|
// scoping behind.
|
||||||
|
prefs.asMap().keys
|
||||||
|
.filter { it.name.startsWith("widget_residence_id_") }
|
||||||
|
.forEach { prefs.remove(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Per-residence widget configuration (gitea#6)
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the user's residences (id + name) as persisted by the main
|
||||||
|
* app. Used by [WidgetConfigActivity] to populate its picker.
|
||||||
|
*/
|
||||||
|
suspend fun readResidencesJson(): String =
|
||||||
|
store.data.first()[WidgetDataStoreKeys.WIDGET_RESIDENCES_JSON] ?: "[]"
|
||||||
|
|
||||||
|
suspend fun writeResidencesJson(json: String) {
|
||||||
|
store.edit { prefs ->
|
||||||
|
prefs[WidgetDataStoreKeys.WIDGET_RESIDENCES_JSON] = json
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the residence id this widget instance is scoped to, or `null`
|
||||||
|
* for "All residences" (no scoping — the legacy default).
|
||||||
|
*/
|
||||||
|
suspend fun readResidenceIdFor(appWidgetId: Int): Long? =
|
||||||
|
store.data.first()[WidgetDataStoreKeys.residenceIdKeyFor(appWidgetId)]
|
||||||
|
|
||||||
|
suspend fun writeResidenceIdFor(appWidgetId: Int, residenceId: Long?) {
|
||||||
|
store.edit { prefs ->
|
||||||
|
val key = WidgetDataStoreKeys.residenceIdKeyFor(appWidgetId)
|
||||||
|
if (residenceId == null) {
|
||||||
|
prefs.remove(key)
|
||||||
|
} else {
|
||||||
|
prefs[key] = residenceId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clear scoping for a removed widget instance (called from `onDeleted`). */
|
||||||
|
suspend fun clearResidenceIdFor(appWidgetId: Int) {
|
||||||
|
store.edit { prefs ->
|
||||||
|
prefs.remove(WidgetDataStoreKeys.residenceIdKeyFor(appWidgetId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,13 @@ interface WidgetRefreshDataSource {
|
|||||||
suspend fun fetchTasks(): ApiResult<List<WidgetTaskDto>>
|
suspend fun fetchTasks(): ApiResult<List<WidgetTaskDto>>
|
||||||
/** Fetch the current user's subscription tier ("free" | "premium"). */
|
/** Fetch the current user's subscription tier ("free" | "premium"). */
|
||||||
suspend fun fetchTier(): String
|
suspend fun fetchTier(): String
|
||||||
|
/**
|
||||||
|
* Fetch the user's residences for the widget configuration picker
|
||||||
|
* (gitea#6). Returning an empty list is non-fatal — the worker will
|
||||||
|
* just skip the residence sidecar update and pre-existing scopes
|
||||||
|
* keep working until next refresh.
|
||||||
|
*/
|
||||||
|
suspend fun fetchResidences(): List<WidgetResidenceDto> = emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,6 +55,16 @@ internal object DefaultWidgetRefreshDataSource : WidgetRefreshDataSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun fetchResidences(): List<WidgetResidenceDto> {
|
||||||
|
val result = APILayer.getMyResidences(forceRefresh = false)
|
||||||
|
return when (result) {
|
||||||
|
is ApiResult.Success -> result.data.residences.map { r ->
|
||||||
|
WidgetResidenceDto(id = r.id.toLong(), name = r.name)
|
||||||
|
}
|
||||||
|
else -> emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun mapToWidgetTasks(response: TaskColumnsResponse): List<WidgetTaskDto> {
|
private fun mapToWidgetTasks(response: TaskColumnsResponse): List<WidgetTaskDto> {
|
||||||
val out = mutableListOf<WidgetTaskDto>()
|
val out = mutableListOf<WidgetTaskDto>()
|
||||||
for (column in response.columns) {
|
for (column in response.columns) {
|
||||||
@@ -112,6 +129,16 @@ class WidgetRefreshWorker(
|
|||||||
val repo = WidgetDataRepository.get(ctx)
|
val repo = WidgetDataRepository.get(ctx)
|
||||||
repo.saveTasks(tasksResult.data)
|
repo.saveTasks(tasksResult.data)
|
||||||
repo.saveTierState(tier)
|
repo.saveTierState(tier)
|
||||||
|
// Best-effort residence sidecar update — failure is
|
||||||
|
// non-fatal because pre-existing scopes (and the
|
||||||
|
// "All residences" fallback) keep working with stale
|
||||||
|
// data until the next refresh succeeds (gitea#6).
|
||||||
|
runCatching {
|
||||||
|
val residences = dataSource.fetchResidences()
|
||||||
|
if (residences.isNotEmpty()) {
|
||||||
|
repo.saveResidences(residences)
|
||||||
|
}
|
||||||
|
}
|
||||||
refreshGlanceWidgets(ctx)
|
refreshGlanceWidgets(ctx)
|
||||||
// Chain the next scheduled refresh so cadence keeps ticking
|
// Chain the next scheduled refresh so cadence keeps ticking
|
||||||
// even if the OS evicts our periodic request. Wrapped in
|
// even if the OS evicts our periodic request. Wrapped in
|
||||||
|
|||||||
@@ -34,6 +34,20 @@ data class WidgetTaskDto(
|
|||||||
val completed: Boolean
|
val completed: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lightweight residence identifier persisted to the widget DataStore.
|
||||||
|
*
|
||||||
|
* Written by the main app whenever [com.tt.honeyDue.data.DataManager.myResidences]
|
||||||
|
* updates so the widget configuration activity can offer the current
|
||||||
|
* residence list (gitea#6 — per-residence widget selection). Mirrors
|
||||||
|
* iOS' `WidgetDataManager.WidgetResidence` shape.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class WidgetResidenceDto(
|
||||||
|
val id: Long,
|
||||||
|
val name: String
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Summary metrics computed from the cached task list.
|
* Summary metrics computed from the cached task list.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -14,4 +14,5 @@
|
|||||||
android:previewLayout="@layout/widget_large_preview"
|
android:previewLayout="@layout/widget_large_preview"
|
||||||
android:description="@string/widget_large_description"
|
android:description="@string/widget_large_description"
|
||||||
android:updatePeriodMillis="1800000"
|
android:updatePeriodMillis="1800000"
|
||||||
|
android:configure="com.tt.honeyDue.widget.WidgetConfigActivity"
|
||||||
android:widgetFeatures="reconfigurable" />
|
android:widgetFeatures="reconfigurable" />
|
||||||
|
|||||||
@@ -14,4 +14,5 @@
|
|||||||
android:previewLayout="@layout/widget_medium_preview"
|
android:previewLayout="@layout/widget_medium_preview"
|
||||||
android:description="@string/widget_medium_description"
|
android:description="@string/widget_medium_description"
|
||||||
android:updatePeriodMillis="1800000"
|
android:updatePeriodMillis="1800000"
|
||||||
|
android:configure="com.tt.honeyDue.widget.WidgetConfigActivity"
|
||||||
android:widgetFeatures="reconfigurable" />
|
android:widgetFeatures="reconfigurable" />
|
||||||
|
|||||||
@@ -14,4 +14,5 @@
|
|||||||
android:previewLayout="@layout/widget_small_preview"
|
android:previewLayout="@layout/widget_small_preview"
|
||||||
android:description="@string/widget_small_description"
|
android:description="@string/widget_small_description"
|
||||||
android:updatePeriodMillis="1800000"
|
android:updatePeriodMillis="1800000"
|
||||||
|
android:configure="com.tt.honeyDue.widget.WidgetConfigActivity"
|
||||||
android:widgetFeatures="reconfigurable" />
|
android:widgetFeatures="reconfigurable" />
|
||||||
|
|||||||
+34
-33
@@ -25,10 +25,14 @@ import kotlin.test.assertTrue
|
|||||||
/**
|
/**
|
||||||
* Unit tests for [CoilAuthInterceptor].
|
* Unit tests for [CoilAuthInterceptor].
|
||||||
*
|
*
|
||||||
|
* Identity is owned by Ory Kratos. Authenticated honeyDue media is gated on
|
||||||
|
* the Kratos session token, carried on the `X-Session-Token` header (the old
|
||||||
|
* `Authorization: Token …` scheme is gone).
|
||||||
|
*
|
||||||
* The interceptor is responsible for:
|
* The interceptor is responsible for:
|
||||||
* 1. Attaching `Authorization: <scheme> <token>` to image requests.
|
* 1. Attaching `X-Session-Token: <token>` to image requests.
|
||||||
* 2. On HTTP 401, calling the refresh callback once and retrying the
|
* 2. On HTTP 401, calling the re-validation callback once and retrying the
|
||||||
* request with the new token.
|
* request with the returned token.
|
||||||
* 3. Not looping: if the retry also returns 401, the error is returned.
|
* 3. Not looping: if the retry also returns 401, the error is returned.
|
||||||
* 4. When no token is available, the request proceeds unauthenticated.
|
* 4. When no token is available, the request proceeds unauthenticated.
|
||||||
*
|
*
|
||||||
@@ -96,7 +100,7 @@ class CoilAuthInterceptorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun interceptor_attaches_authorization_header_when_token_present() = runTest {
|
fun interceptor_attaches_session_token_header_when_token_present() = runTest {
|
||||||
val request = makeRequest()
|
val request = makeRequest()
|
||||||
val chain = FakeChain(
|
val chain = FakeChain(
|
||||||
initialRequest = request,
|
initialRequest = request,
|
||||||
@@ -105,7 +109,6 @@ class CoilAuthInterceptorTest {
|
|||||||
val interceptor = CoilAuthInterceptor(
|
val interceptor = CoilAuthInterceptor(
|
||||||
tokenProvider = { "abc123" },
|
tokenProvider = { "abc123" },
|
||||||
refreshToken = { null },
|
refreshToken = { null },
|
||||||
authScheme = "Token",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val result = interceptor.intercept(chain)
|
val result = interceptor.intercept(chain)
|
||||||
@@ -113,7 +116,9 @@ class CoilAuthInterceptorTest {
|
|||||||
assertTrue(result is SuccessResult, "Expected success result")
|
assertTrue(result is SuccessResult, "Expected success result")
|
||||||
assertEquals(1, chain.capturedRequests.size)
|
assertEquals(1, chain.capturedRequests.size)
|
||||||
val sent = chain.capturedRequests.first()
|
val sent = chain.capturedRequests.first()
|
||||||
assertEquals("Token abc123", sent.httpHeaders["Authorization"])
|
// Token is sent bare (no scheme prefix) on the X-Session-Token header.
|
||||||
|
assertEquals("abc123", sent.httpHeaders["X-Session-Token"])
|
||||||
|
assertNull(sent.httpHeaders["Authorization"], "Legacy Authorization header must not be set")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -126,7 +131,6 @@ class CoilAuthInterceptorTest {
|
|||||||
val interceptor = CoilAuthInterceptor(
|
val interceptor = CoilAuthInterceptor(
|
||||||
tokenProvider = { null },
|
tokenProvider = { null },
|
||||||
refreshToken = { null },
|
refreshToken = { null },
|
||||||
authScheme = "Token",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val result = interceptor.intercept(chain)
|
val result = interceptor.intercept(chain)
|
||||||
@@ -134,12 +138,12 @@ class CoilAuthInterceptorTest {
|
|||||||
assertTrue(result is SuccessResult)
|
assertTrue(result is SuccessResult)
|
||||||
assertEquals(1, chain.capturedRequests.size)
|
assertEquals(1, chain.capturedRequests.size)
|
||||||
val sent = chain.capturedRequests.first()
|
val sent = chain.capturedRequests.first()
|
||||||
// No Authorization header should have been added
|
// No session-token header should have been added.
|
||||||
assertNull(sent.httpHeaders["Authorization"])
|
assertNull(sent.httpHeaders["X-Session-Token"])
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun interceptor_refreshes_and_retries_on_401() = runTest {
|
fun interceptor_revalidates_and_retries_on_401() = runTest {
|
||||||
val request = makeRequest()
|
val request = makeRequest()
|
||||||
var refreshCallCount = 0
|
var refreshCallCount = 0
|
||||||
val chain = FakeChain(
|
val chain = FakeChain(
|
||||||
@@ -150,25 +154,25 @@ class CoilAuthInterceptorTest {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
val interceptor = CoilAuthInterceptor(
|
val interceptor = CoilAuthInterceptor(
|
||||||
tokenProvider = { "old-token" },
|
tokenProvider = { "session-tok" },
|
||||||
refreshToken = {
|
refreshToken = {
|
||||||
refreshCallCount++
|
refreshCallCount++
|
||||||
"new-token"
|
// Kratos session tokens are not rotated — same token echoed back.
|
||||||
|
"session-tok"
|
||||||
},
|
},
|
||||||
authScheme = "Token",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val result = interceptor.intercept(chain)
|
val result = interceptor.intercept(chain)
|
||||||
|
|
||||||
assertTrue(result is SuccessResult, "Expected retry to succeed")
|
assertTrue(result is SuccessResult, "Expected retry to succeed")
|
||||||
assertEquals(1, refreshCallCount, "refreshToken should be invoked exactly once")
|
assertEquals(1, refreshCallCount, "session re-check should be invoked exactly once")
|
||||||
assertEquals(2, chain.capturedRequests.size, "Expected original + 1 retry")
|
assertEquals(2, chain.capturedRequests.size, "Expected original + 1 retry")
|
||||||
assertEquals("Token old-token", chain.capturedRequests[0].httpHeaders["Authorization"])
|
assertEquals("session-tok", chain.capturedRequests[0].httpHeaders["X-Session-Token"])
|
||||||
assertEquals("Token new-token", chain.capturedRequests[1].httpHeaders["Authorization"])
|
assertEquals("session-tok", chain.capturedRequests[1].httpHeaders["X-Session-Token"])
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun interceptor_returns_error_when_refresh_returns_null() = runTest {
|
fun interceptor_returns_error_when_revalidation_returns_null() = runTest {
|
||||||
val request = makeRequest()
|
val request = makeRequest()
|
||||||
var refreshCallCount = 0
|
var refreshCallCount = 0
|
||||||
val chain = FakeChain(
|
val chain = FakeChain(
|
||||||
@@ -176,19 +180,18 @@ class CoilAuthInterceptorTest {
|
|||||||
responses = mutableListOf({ req -> make401Error(req) })
|
responses = mutableListOf({ req -> make401Error(req) })
|
||||||
)
|
)
|
||||||
val interceptor = CoilAuthInterceptor(
|
val interceptor = CoilAuthInterceptor(
|
||||||
tokenProvider = { "old-token" },
|
tokenProvider = { "session-tok" },
|
||||||
refreshToken = {
|
refreshToken = {
|
||||||
refreshCallCount++
|
refreshCallCount++
|
||||||
null
|
null
|
||||||
},
|
},
|
||||||
authScheme = "Token",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val result = interceptor.intercept(chain)
|
val result = interceptor.intercept(chain)
|
||||||
|
|
||||||
assertTrue(result is ErrorResult, "Expected error result when refresh fails")
|
assertTrue(result is ErrorResult, "Expected error result when session is gone")
|
||||||
assertEquals(1, refreshCallCount, "refreshToken should be attempted once")
|
assertEquals(1, refreshCallCount, "session re-check should be attempted once")
|
||||||
// Only the first attempt should have gone through
|
// Only the first attempt should have gone through.
|
||||||
assertEquals(1, chain.capturedRequests.size)
|
assertEquals(1, chain.capturedRequests.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,23 +207,22 @@ class CoilAuthInterceptorTest {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
val interceptor = CoilAuthInterceptor(
|
val interceptor = CoilAuthInterceptor(
|
||||||
tokenProvider = { "old-token" },
|
tokenProvider = { "session-tok" },
|
||||||
refreshToken = {
|
refreshToken = {
|
||||||
refreshCallCount++
|
refreshCallCount++
|
||||||
"new-token"
|
"session-tok"
|
||||||
},
|
},
|
||||||
authScheme = "Token",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val result = interceptor.intercept(chain)
|
val result = interceptor.intercept(chain)
|
||||||
|
|
||||||
assertTrue(result is ErrorResult, "Second 401 should surface as ErrorResult")
|
assertTrue(result is ErrorResult, "Second 401 should surface as ErrorResult")
|
||||||
assertEquals(1, refreshCallCount, "refreshToken should be called exactly once — no infinite loop")
|
assertEquals(1, refreshCallCount, "session re-check should be called exactly once — no infinite loop")
|
||||||
assertEquals(2, chain.capturedRequests.size, "Expected original + exactly one retry")
|
assertEquals(2, chain.capturedRequests.size, "Expected original + exactly one retry")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun interceptor_passes_through_non_401_errors_without_refresh() = runTest {
|
fun interceptor_passes_through_non_401_errors_without_revalidation() = runTest {
|
||||||
val request = makeRequest()
|
val request = makeRequest()
|
||||||
var refreshCallCount = 0
|
var refreshCallCount = 0
|
||||||
val chain = FakeChain(
|
val chain = FakeChain(
|
||||||
@@ -241,33 +243,32 @@ class CoilAuthInterceptorTest {
|
|||||||
refreshCallCount++
|
refreshCallCount++
|
||||||
"should-not-be-called"
|
"should-not-be-called"
|
||||||
},
|
},
|
||||||
authScheme = "Token",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val result = interceptor.intercept(chain)
|
val result = interceptor.intercept(chain)
|
||||||
|
|
||||||
assertTrue(result is ErrorResult)
|
assertTrue(result is ErrorResult)
|
||||||
assertEquals(0, refreshCallCount, "refreshToken should not be invoked on non-401 errors")
|
assertEquals(0, refreshCallCount, "session re-check should not be invoked on non-401 errors")
|
||||||
assertEquals(1, chain.capturedRequests.size)
|
assertEquals(1, chain.capturedRequests.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun interceptor_supports_bearer_scheme() = runTest {
|
fun interceptor_supports_custom_header_name() = runTest {
|
||||||
val request = makeRequest()
|
val request = makeRequest()
|
||||||
val chain = FakeChain(
|
val chain = FakeChain(
|
||||||
initialRequest = request,
|
initialRequest = request,
|
||||||
responses = mutableListOf({ req -> makeSuccess(req) })
|
responses = mutableListOf({ req -> makeSuccess(req) })
|
||||||
)
|
)
|
||||||
val interceptor = CoilAuthInterceptor(
|
val interceptor = CoilAuthInterceptor(
|
||||||
tokenProvider = { "jwt.payload.sig" },
|
tokenProvider = { "tok-value" },
|
||||||
refreshToken = { null },
|
refreshToken = { null },
|
||||||
authScheme = "Bearer",
|
headerName = "X-Custom-Auth",
|
||||||
)
|
)
|
||||||
|
|
||||||
val result = interceptor.intercept(chain)
|
val result = interceptor.intercept(chain)
|
||||||
|
|
||||||
assertTrue(result is SuccessResult)
|
assertTrue(result is SuccessResult)
|
||||||
val sent = chain.capturedRequests.first()
|
val sent = chain.capturedRequests.first()
|
||||||
assertEquals("Bearer jwt.payload.sig", sent.httpHeaders["Authorization"])
|
assertEquals("tok-value", sent.httpHeaders["X-Custom-Auth"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
package com.tt.honeyDue.network
|
||||||
|
|
||||||
|
import com.tt.honeyDue.data.DataManager
|
||||||
|
import com.tt.honeyDue.models.RegisterRequest
|
||||||
|
import com.tt.honeyDue.models.VerifyEmailRequest
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.Assume.assumeTrue
|
||||||
|
import org.junit.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertFalse
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LIVE end-to-end integration test: drives the REAL [AuthApi] (the exact client
|
||||||
|
* code the app runs, OkHttp engine, production URLs from ApiConfig) against the
|
||||||
|
* live honeyDue API + Ory Kratos. No fixtures, no hand-written JSON — every
|
||||||
|
* response is decoded by the real models over the wire, so a contract mismatch
|
||||||
|
* between the API and the KMP client fails here.
|
||||||
|
*
|
||||||
|
* Skipped unless RUN_LIVE_IT=1 (it creates a real throwaway account and needs
|
||||||
|
* network), so it never runs on a normal build/CI. Run explicitly:
|
||||||
|
* RUN_LIVE_IT=1 ./gradlew :composeApp:testDebugUnitTest \
|
||||||
|
* --tests "com.tt.honeyDue.network.LiveAuthIntegrationTest"
|
||||||
|
*/
|
||||||
|
class LiveAuthIntegrationTest {
|
||||||
|
|
||||||
|
private fun liveEnabled() = System.getenv("RUN_LIVE_IT") == "1"
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun passwordSignupFlowAgainstProd() = runBlocking {
|
||||||
|
assumeTrue("set RUN_LIVE_IT=1 to run the live integration test", liveEnabled())
|
||||||
|
|
||||||
|
val api = AuthApi() // ApiClient.httpClient (OkHttp) + PROD URLs
|
||||||
|
val email = "kmpqa-" + System.currentTimeMillis() + "@example.com"
|
||||||
|
val password = "KmpQa1!Test99"
|
||||||
|
|
||||||
|
// 1. Register: admin-create via /api/auth/register/ then immediate login.
|
||||||
|
// Exercises the createAccount HTTP call AND the login decode that
|
||||||
|
// crashed on "continue_with": null.
|
||||||
|
val reg = api.register(
|
||||||
|
RegisterRequest(username = "kmpqa", email = email, password = password, firstName = "Kmp", lastName = "Qa"),
|
||||||
|
)
|
||||||
|
assertTrue(reg is ApiResult.Success, "register should succeed, got: $reg")
|
||||||
|
val auth = (reg as ApiResult.Success).data
|
||||||
|
assertTrue(auth.token.isNotBlank(), "session token must be present")
|
||||||
|
assertEquals(email, auth.user.email)
|
||||||
|
assertFalse(auth.user.verified, "a fresh password account must start unverified")
|
||||||
|
|
||||||
|
// 2. /auth/me decodes through the ApiClient json config (User model).
|
||||||
|
val me = api.getCurrentUser(auth.token)
|
||||||
|
assertTrue(me is ApiResult.Success, "auth/me should succeed, got: $me")
|
||||||
|
assertEquals(email, (me as ApiResult.Success).data.email)
|
||||||
|
|
||||||
|
// 3. Start the client-owned verification flow — decodes the flow body and
|
||||||
|
// stores the flow id; sends the single code.
|
||||||
|
val started = api.startEmailVerification(email)
|
||||||
|
assertTrue(started is ApiResult.Success, "startEmailVerification should succeed, got: $started")
|
||||||
|
assertNotNull(DataManager.pendingVerificationFlowId.value, "a verification flow id must be stored")
|
||||||
|
|
||||||
|
// 4. Submit a deliberately wrong code: must decode the re-rendered flow
|
||||||
|
// and return a clean Error (not a parse crash).
|
||||||
|
val wrong = api.verifyEmail(auth.token, VerifyEmailRequest(code = "000000"))
|
||||||
|
assertTrue(wrong is ApiResult.Error, "wrong code must return Error, got: $wrong")
|
||||||
|
|
||||||
|
DataManager.setPendingVerificationFlowId(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
+140
@@ -0,0 +1,140 @@
|
|||||||
|
package com.tt.honeyDue.widget
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.test.core.app.ApplicationProvider
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertNull
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.robolectric.RobolectricTestRunner
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coverage for the per-residence widget filter added in gitea#6.
|
||||||
|
*
|
||||||
|
* Two surfaces under test:
|
||||||
|
*
|
||||||
|
* 1. `WidgetDataRepository.filterTasksForResidence` — the pure filter
|
||||||
|
* used by `loadTasksForResidence` and (transitively) by the
|
||||||
|
* timeline provider. Mirrors iOS' `WidgetDataManager.filterTasks`.
|
||||||
|
* 2. The per-`appWidgetId` DataStore key — verifies round-tripping
|
||||||
|
* a saved residence id and clearing it for a removed widget.
|
||||||
|
*/
|
||||||
|
@RunWith(RobolectricTestRunner::class)
|
||||||
|
class WidgetResidenceFilterTest {
|
||||||
|
|
||||||
|
private lateinit var context: Context
|
||||||
|
private lateinit var repo: WidgetDataRepository
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() = runTest {
|
||||||
|
context = ApplicationProvider.getApplicationContext()
|
||||||
|
repo = WidgetDataRepository.get(context)
|
||||||
|
repo.clearAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() = runTest {
|
||||||
|
repo.clearAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun task(id: Long, residenceId: Long): WidgetTaskDto =
|
||||||
|
WidgetTaskDto(
|
||||||
|
id = id,
|
||||||
|
title = "Task $id",
|
||||||
|
priority = 0,
|
||||||
|
dueDate = null,
|
||||||
|
isOverdue = false,
|
||||||
|
daysUntilDue = 0,
|
||||||
|
residenceId = residenceId,
|
||||||
|
residenceName = "",
|
||||||
|
categoryIcon = "",
|
||||||
|
completed = false
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- pure filter -------------------------------------------------------
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun filter_nullResidenceReturnsAllTasks() {
|
||||||
|
val tasks = listOf(task(1, 10), task(2, 20), task(3, 30))
|
||||||
|
val result = WidgetDataRepository.filterTasksForResidence(tasks, residenceId = null)
|
||||||
|
assertEquals(listOf(1L, 2L, 3L), result.map { it.id })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun filter_matchingResidenceKeepsOnlyMatchingTasks() {
|
||||||
|
val tasks = listOf(task(1, 10), task(2, 20), task(3, 10), task(4, 30))
|
||||||
|
val result = WidgetDataRepository.filterTasksForResidence(tasks, residenceId = 10)
|
||||||
|
assertEquals(listOf(1L, 3L), result.map { it.id })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun filter_unknownResidenceReturnsEmpty() {
|
||||||
|
val tasks = listOf(task(1, 10), task(2, 20))
|
||||||
|
val result = WidgetDataRepository.filterTasksForResidence(tasks, residenceId = 999)
|
||||||
|
assertTrue(result.isEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun filter_preservesInputOrder() {
|
||||||
|
// Subset only — the timeline provider relies on this so its
|
||||||
|
// own sort step ("overdue first, then by due date") operates
|
||||||
|
// on already-filtered tasks.
|
||||||
|
val tasks = listOf(task(5, 1), task(3, 1), task(7, 1), task(1, 2))
|
||||||
|
val result = WidgetDataRepository.filterTasksForResidence(tasks, residenceId = 1)
|
||||||
|
assertEquals(listOf(5L, 3L, 7L), result.map { it.id })
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- DataStore round-trip ---------------------------------------------
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun perWidgetResidenceId_roundTripsThroughDataStore() = runTest {
|
||||||
|
// Initial state: no scope persisted → returns null ("All residences").
|
||||||
|
assertNull(repo.loadResidenceIdFor(appWidgetId = 42))
|
||||||
|
|
||||||
|
repo.saveResidenceIdFor(appWidgetId = 42, residenceId = 7L)
|
||||||
|
assertEquals(7L, repo.loadResidenceIdFor(appWidgetId = 42))
|
||||||
|
|
||||||
|
// Different widget id stays unscoped — keys are per-instance.
|
||||||
|
assertNull(repo.loadResidenceIdFor(appWidgetId = 99))
|
||||||
|
|
||||||
|
// Save null clears the scope ("All residences" selected after a
|
||||||
|
// previously residence-scoped tile).
|
||||||
|
repo.saveResidenceIdFor(appWidgetId = 42, residenceId = null)
|
||||||
|
assertNull(repo.loadResidenceIdFor(appWidgetId = 42))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun loadTasksForWidget_appliesPerInstanceScope() = runTest {
|
||||||
|
repo.saveTasks(listOf(task(1, 10), task(2, 20), task(3, 10)))
|
||||||
|
repo.saveResidenceIdFor(appWidgetId = 1, residenceId = 10L)
|
||||||
|
repo.saveResidenceIdFor(appWidgetId = 2, residenceId = 20L)
|
||||||
|
|
||||||
|
assertEquals(listOf(1L, 3L), repo.loadTasksForWidget(1).map { it.id })
|
||||||
|
assertEquals(listOf(2L), repo.loadTasksForWidget(2).map { it.id })
|
||||||
|
// Unconfigured tile defaults to "All residences" — every task.
|
||||||
|
assertEquals(listOf(1L, 2L, 3L), repo.loadTasksForWidget(3).map { it.id })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun clearResidenceIdFor_dropsScope() = runTest {
|
||||||
|
repo.saveResidenceIdFor(appWidgetId = 5, residenceId = 11L)
|
||||||
|
assertEquals(11L, repo.loadResidenceIdFor(5))
|
||||||
|
|
||||||
|
repo.clearResidenceIdFor(5)
|
||||||
|
assertNull(repo.loadResidenceIdFor(5))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun saveResidences_roundTripsResidenceList() = runTest {
|
||||||
|
val payload = listOf(
|
||||||
|
WidgetResidenceDto(id = 1, name = "Home"),
|
||||||
|
WidgetResidenceDto(id = 2, name = "Cabin")
|
||||||
|
)
|
||||||
|
repo.saveResidences(payload)
|
||||||
|
assertEquals(payload, repo.loadResidences())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -472,4 +472,585 @@
|
|||||||
<string name="subscription_features">Premium-Funktionen</string>
|
<string name="subscription_features">Premium-Funktionen</string>
|
||||||
<string name="subscription_limit_properties">Sie haben das Immobilienlimit Ihres Plans erreicht</string>
|
<string name="subscription_limit_properties">Sie haben das Immobilienlimit Ihres Plans erreicht</string>
|
||||||
<string name="subscription_limit_tasks">Sie haben das Aufgabenlimit Ihres Plans erreicht</string>
|
<string name="subscription_limit_tasks">Sie haben das Aufgabenlimit Ihres Plans erreicht</string>
|
||||||
|
<string name="home_profile_heating">Heizung</string>
|
||||||
|
<string name="home_profile_cooling">Kühlung</string>
|
||||||
|
<string name="home_profile_water_heater">Warmwasserbereiter</string>
|
||||||
|
<string name="home_profile_pool">Pool</string>
|
||||||
|
<string name="home_profile_sprinkler_system">Bewässerungsanlage</string>
|
||||||
|
<string name="home_profile_fireplace">Kamin</string>
|
||||||
|
<string name="home_profile_garage">Garage</string>
|
||||||
|
<string name="home_profile_basement">Keller</string>
|
||||||
|
<string name="home_profile_attic">Dachboden</string>
|
||||||
|
<string name="home_profile_septic">Klärgrube</string>
|
||||||
|
<string name="home_profile_roof_type">Dachtyp</string>
|
||||||
|
<string name="home_profile_exterior">Außenbereich</string>
|
||||||
|
<string name="home_profile_flooring">Bodenbelag</string>
|
||||||
|
<string name="home_profile_landscaping">Garten</string>
|
||||||
|
<string name="onboarding_first_task_selected_count">%1$d Aufgaben ausgewählt</string>
|
||||||
|
<string name="onboarding_first_task_add_continue">%1$d Aufgaben hinzufügen & weiter</string>
|
||||||
|
<string name="onboarding_first_task_finding">Aufgaben für dein Zuhause werden gesucht...</string>
|
||||||
|
<string name="onboarding_first_task_no_suggestions">Noch keine persönlichen Vorschläge — durchsuche den vollständigen Katalog oder überspringe diesen Schritt.</string>
|
||||||
|
<string name="onboarding_first_task_browse_all">Alle ansehen</string>
|
||||||
|
<string name="onboarding_first_task_skip">Überspringen</string>
|
||||||
|
<string name="onboarding_first_task_suggestions_error">Deine Vorschläge konnten nicht geladen werden</string>
|
||||||
|
<string name="onboarding_first_task_connection_error">Prüfe deine Verbindung und versuche es erneut.</string>
|
||||||
|
<string name="onboarding_first_task_loading_catalog">Aufgabenkatalog wird geladen...</string>
|
||||||
|
<string name="onboarding_first_task_catalog_error">Aufgabenkatalog konnte nicht geladen werden</string>
|
||||||
|
<string name="onboarding_first_task_no_templates">Derzeit keine Vorlagen verfügbar.</string>
|
||||||
|
<string name="onboarding_first_task_selected">Ausgewählt</string>
|
||||||
|
<string name="onboarding_first_task_offline">Offline</string>
|
||||||
|
<string name="onboarding_first_task_skip_for_now">Vorerst überspringen</string>
|
||||||
|
<string name="upgrade_hero_title">Auf Pro upgraden</string>
|
||||||
|
<string name="upgrade_hero_subtitle">Hol das Beste aus honeyDue heraus</string>
|
||||||
|
<string name="upgrade_choose_plan">Wähle deinen Tarif</string>
|
||||||
|
<string name="upgrade_plan_yearly">Jährlich</string>
|
||||||
|
<string name="upgrade_plan_monthly">Monatlich</string>
|
||||||
|
<string name="upgrade_plan_save_50">Spare 50 %</string>
|
||||||
|
<string name="upgrade_best_value">BESTER WERT</string>
|
||||||
|
<string name="upgrade_whats_included">Was enthalten ist</string>
|
||||||
|
<string name="upgrade_feature_unlimited_properties">Unbegrenzte Objekte</string>
|
||||||
|
<string name="upgrade_feature_unlimited_properties_desc">Verwalte die Wartung all deiner Häuser</string>
|
||||||
|
<string name="upgrade_feature_unlimited_tasks">Unbegrenzte Aufgaben</string>
|
||||||
|
<string name="upgrade_feature_unlimited_tasks_desc">Vergiss nie wieder eine Wartungsaufgabe</string>
|
||||||
|
<string name="upgrade_feature_contractor_management">Handwerkerverwaltung</string>
|
||||||
|
<string name="upgrade_feature_contractor_management_desc">Speichere und bewerte deine vertrauten Handwerker</string>
|
||||||
|
<string name="upgrade_feature_document_vault">Dokumententresor</string>
|
||||||
|
<string name="upgrade_feature_document_vault_desc">Bewahre Garantien, Belege und Anleitungen auf</string>
|
||||||
|
<string name="upgrade_feature_family_sharing">Familienfreigabe</string>
|
||||||
|
<string name="upgrade_feature_family_sharing_desc">Lade Familienmitglieder zur Zusammenarbeit ein</string>
|
||||||
|
<string name="upgrade_feature_smart_reminders">Smarte Erinnerungen</string>
|
||||||
|
<string name="upgrade_feature_smart_reminders_desc">Werde benachrichtigt, wenn Aufgaben fällig sind</string>
|
||||||
|
<string name="upgrade_feature_document_warranty_storage">Dokument- & Garantie-Speicher</string>
|
||||||
|
<string name="upgrade_subscribe_now">Jetzt abonnieren</string>
|
||||||
|
<string name="upgrade_restore_purchases">Käufe wiederherstellen</string>
|
||||||
|
<string name="upgrade_terms_text">Das Abo verlängert sich automatisch, sofern es nicht mindestens 24 Stunden vor Ende des aktuellen Zeitraums gekündigt wird. Verwalte Abos in deinen Geräteeinstellungen.</string>
|
||||||
|
<string name="upgrade_terms_of_use">Nutzungsbedingungen</string>
|
||||||
|
<string name="upgrade_included">Enthalten</string>
|
||||||
|
<string name="upgrade_compare_free_vs_pro">Free vs. Pro vergleichen</string>
|
||||||
|
<string name="upgrade_maybe_later">Vielleicht später</string>
|
||||||
|
<string name="upgrade_warning">Warnung</string>
|
||||||
|
<string name="upgrade_subscription_active">Abo aktiv</string>
|
||||||
|
<string name="upgrade_subscription_active_message">Du hast jetzt vollen Zugriff auf alle Pro-Funktionen!</string>
|
||||||
|
<string name="upgrade_feature_required_title">Upgrade erforderlich</string>
|
||||||
|
<string name="upgrade_feature_required_message">Diese Funktion ist mit einem Pro-Abo verfügbar.</string>
|
||||||
|
<string name="upgrade_prompt_default_message">Schalte unbegrenzten Zugriff auf alle Funktionen frei</string>
|
||||||
|
<string name="upgrade_product_monthly_name">honeyDue Pro Monatlich</string>
|
||||||
|
<string name="upgrade_product_annual_name">honeyDue Pro Jährlich</string>
|
||||||
|
<string name="upgrade_billed_monthly">Monatliche Abrechnung</string>
|
||||||
|
<string name="upgrade_billed_annually">Jährliche Abrechnung</string>
|
||||||
|
<string name="upgrade_save_22">Spare 22 %</string>
|
||||||
|
<string name="tasks_column_done">Erledigt</string>
|
||||||
|
<string name="tasks_column_archived">Archiviert</string>
|
||||||
|
<string name="tasks_column_empty">Keine Aufgaben</string>
|
||||||
|
<string name="tasks_new_title">Neue Aufgabe</string>
|
||||||
|
<string name="tasks_title_field_label">Titel</string>
|
||||||
|
<string name="tasks_title_placeholder">z. B. Warmwasserbereiter spülen</string>
|
||||||
|
<string name="tasks_description_placeholder">Optionale Details</string>
|
||||||
|
<string name="tasks_due_date_optional_label">Fälligkeitsdatum (optional)</string>
|
||||||
|
<string name="tasks_due_date_placeholder_format">yyyy-MM-dd</string>
|
||||||
|
<string name="tasks_due_date_blank_helper">Leer lassen für kein Fälligkeitsdatum</string>
|
||||||
|
<string name="tasks_estimated_cost_optional_label">Geschätzte Kosten (optional)</string>
|
||||||
|
<string name="suggestions_title">Vorgeschlagene Aufgaben</string>
|
||||||
|
<string name="suggestions_skip">Überspringen</string>
|
||||||
|
<string name="suggestions_accept">Übernehmen</string>
|
||||||
|
<string name="suggestions_load_failed">Vorschläge konnten nicht geladen werden</string>
|
||||||
|
<string name="suggestions_empty_title">Noch keine Vorschläge</string>
|
||||||
|
<string name="suggestions_empty_subtitle">Vervollständige dein Zuhause-Profil, um persönliche Empfehlungen zu sehen.</string>
|
||||||
|
<string name="completion_history_title">Erledigungsverlauf</string>
|
||||||
|
<string name="completion_history_count_one">%1$d Erledigung</string>
|
||||||
|
<string name="completion_history_count_other">%1$d Erledigungen</string>
|
||||||
|
<string name="completion_history_loading">Erledigungen werden geladen...</string>
|
||||||
|
<string name="completion_history_load_failed">Erledigungen konnten nicht geladen werden</string>
|
||||||
|
<string name="completion_history_empty_title">Noch keine Erledigungen</string>
|
||||||
|
<string name="completion_history_empty_message">Diese Aufgabe wurde noch nicht erledigt.</string>
|
||||||
|
<string name="completion_history_completed_by">Erledigt von %1$s</string>
|
||||||
|
<string name="completion_history_view_photo">Foto ansehen</string>
|
||||||
|
<string name="manage_users_remove_user">Benutzer entfernen</string>
|
||||||
|
<string name="manage_users_copy_code">Code kopieren</string>
|
||||||
|
<string name="manage_users_code_copied">Code in die Zwischenablage kopiert</string>
|
||||||
|
<string name="manage_users_load_failed">Benutzer konnten nicht geladen werden</string>
|
||||||
|
<string name="manage_users_remove_confirm">%1$s aus diesem Objekt entfernen?</string>
|
||||||
|
<string name="properties_shared_users_count">Freigegebene Benutzer (%1$d)</string>
|
||||||
|
<string name="properties_no_shared_users">Keine freigegebenen Benutzer</string>
|
||||||
|
<string name="properties_shared_users_helper">Benutzer mit Zugriff auf dieses Objekt. Nutze die Teilen-Schaltfläche, um andere einzuladen.</string>
|
||||||
|
<string name="properties_remove_user_confirm">Möchtest du %1$s wirklich aus diesem Objekt entfernen?</string>
|
||||||
|
<string name="properties_remove_button">Entfernen</string>
|
||||||
|
<string name="documents_expires_label">Läuft ab</string>
|
||||||
|
<string name="auth_password_requirement_length">Mindestens 8 Zeichen</string>
|
||||||
|
<string name="auth_password_requirement_uppercase">Enthält einen Großbuchstaben</string>
|
||||||
|
<string name="auth_password_requirement_lowercase">Enthält einen Kleinbuchstaben</string>
|
||||||
|
<string name="auth_password_requirement_digit">Enthält eine Zahl</string>
|
||||||
|
<string name="auth_password_requirement_match">Passwörter stimmen überein</string>
|
||||||
|
<string name="auth_password_requirements_title">Passwortanforderungen</string>
|
||||||
|
<string name="auth_password_complexity_error">Das Passwort muss mindestens 8 Zeichen lang sein und mindestens einen Groß-, einen Kleinbuchstaben und eine Zahl enthalten</string>
|
||||||
|
<string name="properties_join_residence_title">Wohnsitz beitreten</string>
|
||||||
|
<string name="properties_join_residence_message">Möchtest du diesem geteilten Wohnsitz beitreten?</string>
|
||||||
|
<string name="properties_join_success">Beigetreten</string>
|
||||||
|
<string name="properties_join_success_message">Du hast jetzt Zugriff auf %1$s.</string>
|
||||||
|
<string name="properties_join_failed">Beitritt fehlgeschlagen</string>
|
||||||
|
<string name="properties_joining">Beitreten...</string>
|
||||||
|
<string name="properties_shared_by">Geteilt von: %1$s</string>
|
||||||
|
<string name="properties_expires">Läuft ab: %1$s</string>
|
||||||
|
<string name="properties_share_upgrade_title">Pro-Funktion</string>
|
||||||
|
<string name="properties_share_upgrade_message">Das Teilen von Wohnsitzen ist eine Pro-Funktion. Upgrade, um Familienmitglieder zur gemeinsamen Hauspflege einzuladen.</string>
|
||||||
|
<string name="tasks_failed_to_cancel">Aufgabe konnte nicht abgebrochen werden</string>
|
||||||
|
<string name="tasks_failed_to_restore">Aufgabe konnte nicht wiederhergestellt werden</string>
|
||||||
|
<string name="tasks_failed_to_mark_in_progress">Aufgabe konnte nicht als in Bearbeitung markiert werden</string>
|
||||||
|
<string name="tasks_failed_to_archive">Aufgabe konnte nicht archiviert werden</string>
|
||||||
|
<string name="tasks_failed_to_unarchive">Archivierung konnte nicht aufgehoben werden</string>
|
||||||
|
<string name="tasks_card_in_progress">IN BEARBEITUNG</string>
|
||||||
|
<string name="tasks_card_actions">Aktionen</string>
|
||||||
|
<string name="tasks_card_mark_in_progress">Als in Bearbeitung markieren</string>
|
||||||
|
<string name="tasks_card_complete_task">Aufgabe abschließen</string>
|
||||||
|
<string name="tasks_card_edit_task">Aufgabe bearbeiten</string>
|
||||||
|
<string name="tasks_card_cancel_task">Aufgabe abbrechen</string>
|
||||||
|
<string name="tasks_card_restore_task">Aufgabe wiederherstellen</string>
|
||||||
|
<string name="tasks_card_archive_task">Aufgabe archivieren</string>
|
||||||
|
<string name="tasks_card_unarchive_task">Archivierung aufheben</string>
|
||||||
|
<string name="tasks_card_not_available">—</string>
|
||||||
|
<string name="tasks_card_completed_by">Von: %1$s</string>
|
||||||
|
<string name="tasks_card_cost">Kosten: %1$s $</string>
|
||||||
|
<string name="tasks_card_view_photos">Fotos ansehen (%1$d)</string>
|
||||||
|
<string name="tasks_add_new">Neue Aufgabe</string>
|
||||||
|
<string name="tasks_property_required">Objekt *</string>
|
||||||
|
<string name="tasks_property_error">Objekt ist erforderlich</string>
|
||||||
|
<string name="tasks_browse_templates">Aufgabenvorlagen durchsuchen</string>
|
||||||
|
<string name="tasks_common_tasks">%1$d gängige Aufgaben</string>
|
||||||
|
<string name="tasks_category_error">Kategorie ist erforderlich</string>
|
||||||
|
<string name="tasks_interval_days">Intervalltage</string>
|
||||||
|
<string name="tasks_interval_override">Standardintervall überschreiben</string>
|
||||||
|
<string name="tasks_custom_interval_help">Anzahl der Tage zwischen den Durchgängen</string>
|
||||||
|
<string name="tasks_due_date_format_error">Fälligkeitsdatum ist erforderlich (Format: JJJJ-MM-TT)</string>
|
||||||
|
<string name="tasks_due_date_format">Format: JJJJ-MM-TT</string>
|
||||||
|
<string name="tasks_create">Aufgabe erstellen</string>
|
||||||
|
<string name="tasks_in_progress_label">In Bearbeitung</string>
|
||||||
|
<string name="templates_title">Aufgabenvorlagen</string>
|
||||||
|
<string name="templates_done">Fertig</string>
|
||||||
|
<string name="templates_search_placeholder">Vorlagen suchen...</string>
|
||||||
|
<string name="templates_clear">Löschen</string>
|
||||||
|
<string name="templates_result">Ergebnis</string>
|
||||||
|
<string name="templates_results">Ergebnisse</string>
|
||||||
|
<string name="templates_no_results_title">Keine Vorlagen gefunden</string>
|
||||||
|
<string name="templates_no_results_message">Versuche einen anderen Suchbegriff</string>
|
||||||
|
<string name="templates_empty_title">Keine Vorlagen verfügbar</string>
|
||||||
|
<string name="templates_empty_message">Vorlagen erscheinen hier, sobald sie geladen sind</string>
|
||||||
|
<string name="templates_expand">Aufklappen</string>
|
||||||
|
<string name="templates_collapse">Zuklappen</string>
|
||||||
|
<string name="templates_add">Hinzufügen</string>
|
||||||
|
<string name="templates_all_categories">Alle</string>
|
||||||
|
<string name="templates_apply">Anwenden</string>
|
||||||
|
<string name="templates_apply_count">Anwenden (%1$d)</string>
|
||||||
|
<string name="templates_selected_count">%1$d ausgewählt</string>
|
||||||
|
<string name="templates_retry">Erneut versuchen</string>
|
||||||
|
<string name="templates_load_failed">Vorlagen konnten nicht geladen werden</string>
|
||||||
|
<string name="templates_create_failed">Aufgaben konnten nicht erstellt werden</string>
|
||||||
|
<string name="completions_complete_task_title">Aufgabe abschließen: %1$s</string>
|
||||||
|
<string name="completions_select_contractor">Dienstleister wählen (optional)</string>
|
||||||
|
<string name="completions_choose_contractor_placeholder">Dienstleister wählen oder leer lassen</string>
|
||||||
|
<string name="completions_expand">Aufklappen</string>
|
||||||
|
<string name="completions_none_manual">Keiner (manuelle Eingabe)</string>
|
||||||
|
<string name="completions_loading_contractors">Dienstleister werden geladen...</string>
|
||||||
|
<string name="completions_error_loading_contractors">Fehler beim Laden der Dienstleister</string>
|
||||||
|
<string name="completions_completed_by_name">Erledigt von (optional)</string>
|
||||||
|
<string name="completions_completed_by_placeholder">Name eingeben, falls kein Dienstleister oben</string>
|
||||||
|
<string name="completions_actual_cost_optional">Tatsächliche Kosten (optional)</string>
|
||||||
|
<string name="completions_notes_optional">Notizen (optional)</string>
|
||||||
|
<string name="completions_rating">Bewertung: %1$d von 5</string>
|
||||||
|
<string name="completions_add_images">Bilder hinzufügen</string>
|
||||||
|
<string name="completions_take_photo">Foto aufnehmen</string>
|
||||||
|
<string name="completions_choose_from_library">Aus Galerie wählen</string>
|
||||||
|
<string name="completions_images_selected">%1$d Bild(er) ausgewählt</string>
|
||||||
|
<string name="completions_remove_image">Bild entfernen</string>
|
||||||
|
<string name="completions_complete_button">Abschließen</string>
|
||||||
|
<string name="completions_quality_rating">Qualitätsbewertung</string>
|
||||||
|
<string name="completions_photos_count">Fotos (%1$d/%2$d)</string>
|
||||||
|
<string name="completions_camera">Kamera</string>
|
||||||
|
<string name="completions_library">Galerie</string>
|
||||||
|
<string name="completions_add_photos_helper">Fotos der erledigten Arbeit hinzufügen (optional)</string>
|
||||||
|
<string name="completions_contractor_helper">Diesen Abschluss mit einem Dienstleister verknüpfen</string>
|
||||||
|
<string name="completions_details_section">Abschlussdetails</string>
|
||||||
|
<string name="completions_optional_info">Alle Felder sind optional</string>
|
||||||
|
<string name="completions_notes_helper">Notizen zur erledigten Arbeit hinzufügen</string>
|
||||||
|
<string name="completions_notes_placeholder">Beschreibe die Arbeit, gefundene Probleme usw.</string>
|
||||||
|
<string name="completions_rate_quality">Bewerte die Qualität der Arbeit</string>
|
||||||
|
<string name="completions_enter_manually">Name unten manuell eingeben</string>
|
||||||
|
<string name="manage_users_title">Nutzer verwalten</string>
|
||||||
|
<string name="manage_users_invite_title">Andere einladen</string>
|
||||||
|
<string name="manage_users_easy_share">Einfach teilen</string>
|
||||||
|
<string name="manage_users_send_invite">Einladungslink senden</string>
|
||||||
|
<string name="manage_users_easy_share_desc">Sende eine .honeydue-Datei per Nachricht, E-Mail oder teilen. Ein Tippen genügt zum Beitreten.</string>
|
||||||
|
<string name="manage_users_share_code">Code teilen</string>
|
||||||
|
<string name="manage_users_no_code">Kein aktiver Code</string>
|
||||||
|
<string name="manage_users_generate">Code erstellen</string>
|
||||||
|
<string name="manage_users_generate_new">Neuen Code erstellen</string>
|
||||||
|
<string name="manage_users_code_desc">Teile diesen 6-stelligen Code. Er kann in der App eingegeben werden, um beizutreten.</string>
|
||||||
|
<string name="manage_users_users_count">Nutzer (%1$d)</string>
|
||||||
|
<string name="manage_users_owner_badge">Eigentümer</string>
|
||||||
|
<string name="manage_users_remove">Entfernen</string>
|
||||||
|
<string name="manage_users_or">oder</string>
|
||||||
|
<string name="contractors_share">Dienstleister teilen</string>
|
||||||
|
<string name="contractors_share_upgrade_title">Pro-Funktion</string>
|
||||||
|
<string name="contractors_share_upgrade_message">Das Teilen von Dienstleistern ist eine Pro-Funktion. Upgrade, um deine vertrauten Dienstleister mit Freunden und Familie zu teilen.</string>
|
||||||
|
<string name="contractors_import_title">Dienstleister importieren</string>
|
||||||
|
<string name="contractors_import_message">Möchtest du diesen Dienstleister importieren?</string>
|
||||||
|
<string name="contractors_import_success">Dienstleister importiert</string>
|
||||||
|
<string name="contractors_import_success_message">%1$s wurde zu deinen Kontakten hinzugefügt.</string>
|
||||||
|
<string name="contractors_import_failed">Import fehlgeschlagen</string>
|
||||||
|
<string name="contractors_shared_by">Geteilt von: %1$s</string>
|
||||||
|
<string name="contractors_form_add_title">Dienstleister hinzufügen</string>
|
||||||
|
<string name="contractors_form_edit_title">Dienstleister bearbeiten</string>
|
||||||
|
<string name="contractors_form_basic_info">Grundinformationen</string>
|
||||||
|
<string name="contractors_form_name_required">Name *</string>
|
||||||
|
<string name="contractors_form_company">Firma</string>
|
||||||
|
<string name="contractors_form_residence_optional">Wohnsitz (optional)</string>
|
||||||
|
<string name="contractors_form_personal_no_residence">Persönlich (kein Wohnsitz)</string>
|
||||||
|
<string name="contractors_form_personal_visibility">Nur du siehst diesen Dienstleister</string>
|
||||||
|
<string name="contractors_form_shared_visibility">Alle Nutzer von %1$s sehen diesen Dienstleister</string>
|
||||||
|
<string name="contractors_form_contact_info">Kontaktinformationen</string>
|
||||||
|
<string name="contractors_form_phone">Telefon</string>
|
||||||
|
<string name="contractors_form_email">E-Mail</string>
|
||||||
|
<string name="contractors_form_website">Webseite</string>
|
||||||
|
<string name="contractors_form_specialties">Fachgebiete</string>
|
||||||
|
<string name="contractors_form_address_section">Adresse</string>
|
||||||
|
<string name="contractors_form_street_address">Straße</string>
|
||||||
|
<string name="contractors_form_city">Stadt</string>
|
||||||
|
<string name="contractors_form_state">Bundesland</string>
|
||||||
|
<string name="contractors_form_zip_code">Postleitzahl</string>
|
||||||
|
<string name="contractors_form_notes_section">Notizen</string>
|
||||||
|
<string name="contractors_form_private_notes">Private Notizen</string>
|
||||||
|
<string name="contractors_form_mark_favorite">Als Favorit markieren</string>
|
||||||
|
<string name="contractors_form_add_button">Hinzufügen</string>
|
||||||
|
<string name="contractors_form_save_button">Speichern</string>
|
||||||
|
<string name="documents_form_edit_warranty">Garantie bearbeiten</string>
|
||||||
|
<string name="documents_form_edit_document">Dokument bearbeiten</string>
|
||||||
|
<string name="documents_form_add_warranty">Garantie hinzufügen</string>
|
||||||
|
<string name="documents_form_add_document">Dokument hinzufügen</string>
|
||||||
|
<string name="documents_form_select_residence">Wohnsitz wählen</string>
|
||||||
|
<string name="documents_form_residence_required">Wohnsitz *</string>
|
||||||
|
<string name="documents_form_document_type_required">Dokumenttyp *</string>
|
||||||
|
<string name="documents_form_title_required">Titel *</string>
|
||||||
|
<string name="documents_form_item_name_required">Artikelname *</string>
|
||||||
|
<string name="documents_form_model_number">Modellnummer</string>
|
||||||
|
<string name="documents_form_serial_number">Seriennummer</string>
|
||||||
|
<string name="documents_form_provider_required">Anbieter/Firma *</string>
|
||||||
|
<string name="documents_form_provider_contact">Anbieterkontakt</string>
|
||||||
|
<string name="documents_form_claim_phone">Telefon für Ansprüche</string>
|
||||||
|
<string name="documents_form_claim_email">E-Mail für Ansprüche</string>
|
||||||
|
<string name="documents_form_claim_website">Webseite für Ansprüche</string>
|
||||||
|
<string name="documents_form_purchase_date">Kaufdatum (JJJJ-MM-TT)</string>
|
||||||
|
<string name="documents_form_warranty_start">Garantiebeginn (JJJJ-MM-TT)</string>
|
||||||
|
<string name="documents_form_warranty_end_required">Garantieende (JJJJ-MM-TT) *</string>
|
||||||
|
<string name="documents_form_description">Beschreibung</string>
|
||||||
|
<string name="documents_form_category">Kategorie</string>
|
||||||
|
<string name="documents_form_select_category">Kategorie wählen</string>
|
||||||
|
<string name="documents_form_category_none">Keine</string>
|
||||||
|
<string name="documents_form_tags">Tags</string>
|
||||||
|
<string name="documents_form_tags_placeholder">tag1, tag2, tag3</string>
|
||||||
|
<string name="documents_form_notes">Notizen</string>
|
||||||
|
<string name="documents_form_active">Aktiv</string>
|
||||||
|
<string name="documents_form_existing_photos">Vorhandene Fotos (%1$d)</string>
|
||||||
|
<string name="documents_form_new_photos">Neue Fotos (%1$d/%2$d)</string>
|
||||||
|
<string name="documents_form_photos">Fotos (%1$d/%2$d)</string>
|
||||||
|
<string name="documents_form_camera">Kamera</string>
|
||||||
|
<string name="documents_form_gallery">Galerie</string>
|
||||||
|
<string name="documents_form_image_number">Bild %1$d</string>
|
||||||
|
<string name="documents_form_remove_image">Bild entfernen</string>
|
||||||
|
<string name="documents_form_update_warranty">Garantie aktualisieren</string>
|
||||||
|
<string name="documents_form_update_document">Dokument aktualisieren</string>
|
||||||
|
<string name="documents_form_select_residence_error">Bitte wähle einen Wohnsitz</string>
|
||||||
|
<string name="documents_form_title_error">Titel ist erforderlich</string>
|
||||||
|
<string name="documents_form_item_name_error">Artikelname ist für Garantien erforderlich</string>
|
||||||
|
<string name="documents_form_provider_error">Anbieter ist für Garantien erforderlich</string>
|
||||||
|
<string name="documents_form_date_placeholder">2024-01-15</string>
|
||||||
|
<string name="documents_form_date_placeholder_end">2025-01-15</string>
|
||||||
|
<string name="documents_form_failed_to_load_residences">Wohnsitze konnten nicht geladen werden: %1$s</string>
|
||||||
|
<string name="profile_support">Support</string>
|
||||||
|
<string name="profile_contact_support">Support kontaktieren</string>
|
||||||
|
<string name="profile_contact_support_subtitle">Hilfe zu deinem Konto erhalten</string>
|
||||||
|
<string name="profile_upgrade_benefits_title">Premium-Funktionen freischalten</string>
|
||||||
|
<string name="profile_upgrade_benefits_subtitle">Upgrade auf Pro für das volle Erlebnis</string>
|
||||||
|
<string name="profile_benefit_unlimited_properties">Unbegrenzte Objekte</string>
|
||||||
|
<string name="profile_benefit_document_vault">Dokument- & Garantiespeicher</string>
|
||||||
|
<string name="profile_benefit_residence_sharing">Wohnsitz teilen</string>
|
||||||
|
<string name="profile_benefit_contractor_sharing">Dienstleister teilen</string>
|
||||||
|
<string name="profile_benefit_actionable_notifications">Aktive Benachrichtigungen</string>
|
||||||
|
<string name="profile_benefit_widgets">Startbildschirm-Widgets</string>
|
||||||
|
<string name="profile_privacy">Datenschutzrichtlinie</string>
|
||||||
|
<string name="profile_privacy_subtitle">Unsere Datenschutzrichtlinie ansehen</string>
|
||||||
|
<string name="profile_app_version">Version %1$s</string>
|
||||||
|
<string name="profile_app_name">honeyDue</string>
|
||||||
|
<string name="profile_edit_profile">Profil bearbeiten</string>
|
||||||
|
<string name="delete_account_title">Konto löschen</string>
|
||||||
|
<string name="delete_account_subtitle">Dein Konto dauerhaft löschen</string>
|
||||||
|
<string name="delete_account_warning">Diese Aktion ist endgültig und kann nicht rückgängig gemacht werden. Alle deine Daten werden gelöscht.</string>
|
||||||
|
<string name="delete_account_shared_warning">Alle Wohnsitze, die dir gehören und mit anderen geteilt sind, werden ebenfalls gelöscht.</string>
|
||||||
|
<string name="delete_account_confirm_password">Gib dein Passwort zur Bestätigung ein</string>
|
||||||
|
<string name="delete_account_confirm_type">Tippe DELETE zur Bestätigung</string>
|
||||||
|
<string name="delete_account_button">Mein Konto löschen</string>
|
||||||
|
<string name="delete_account_cancel">Abbrechen</string>
|
||||||
|
<string name="delete_account_success">Konto erfolgreich gelöscht</string>
|
||||||
|
<string name="delete_account_failed">Konto konnte nicht gelöscht werden</string>
|
||||||
|
<string name="notifications_daily_digest">Tägliche Übersicht</string>
|
||||||
|
<string name="notifications_daily_digest_desc">Tägliche Übersicht über fällige und überfällige Aufgaben</string>
|
||||||
|
<string name="notifications_email_section">E-Mail-Benachrichtigungen</string>
|
||||||
|
<string name="notifications_email_task_completed">E-Mail bei Abschluss</string>
|
||||||
|
<string name="notifications_email_task_completed_desc">E-Mail erhalten, wenn eine Aufgabe abgeschlossen wird</string>
|
||||||
|
<string name="notifications_set_custom_time">Eigene Zeit festlegen</string>
|
||||||
|
<string name="notifications_change_time">Ändern</string>
|
||||||
|
<string name="notifications_select_time">Benachrichtigungszeit wählen</string>
|
||||||
|
<string name="notifications_master_title">Alle Benachrichtigungen</string>
|
||||||
|
<string name="notifications_master_desc">Alle Kategorien mit einem Tippen ein- oder ausschalten</string>
|
||||||
|
<string name="notifications_categories_section">Kategorien</string>
|
||||||
|
<string name="notifications_category_task_reminder">Aufgabenerinnerungen</string>
|
||||||
|
<string name="notifications_category_task_reminder_desc">Erinnerungen an anstehende und bald fällige Aufgaben</string>
|
||||||
|
<string name="notifications_category_task_overdue">Überfällige Aufgaben</string>
|
||||||
|
<string name="notifications_category_task_overdue_desc">Hinweise, wenn eine Aufgabe überfällig ist</string>
|
||||||
|
<string name="notifications_category_residence_invite">Wohnsitz-Einladungen</string>
|
||||||
|
<string name="notifications_category_residence_invite_desc">Einladungen zu einem geteilten Wohnsitz</string>
|
||||||
|
<string name="notifications_category_subscription">Abo-Updates</string>
|
||||||
|
<string name="notifications_category_subscription_desc">Änderungen bei Abrechnung und Tarifstatus</string>
|
||||||
|
<string name="notifications_open_system_settings">Systemeinstellungen öffnen</string>
|
||||||
|
<string name="notifications_system_settings_desc">Töne, Badges und „Nicht stören“ in den Android-Einstellungen feinabstimmen</string>
|
||||||
|
<string name="common_share">Teilen</string>
|
||||||
|
<string name="common_import">Importieren</string>
|
||||||
|
<string name="common_importing">Importieren...</string>
|
||||||
|
<string name="common_try_again">Erneut versuchen</string>
|
||||||
|
<string name="home_overdue">Überfällig</string>
|
||||||
|
<string name="home_due_this_week">Diese Woche fällig</string>
|
||||||
|
<string name="home_next_30_days">Nächste 30 Tage</string>
|
||||||
|
<string name="home_your_properties">Deine Objekte</string>
|
||||||
|
<string name="onboarding_welcome_title">Willkommen bei honeyDue</string>
|
||||||
|
<string name="onboarding_welcome_subtitle">Dein Begleiter für die Hauspflege</string>
|
||||||
|
<string name="onboarding_start_fresh">Neu starten</string>
|
||||||
|
<string name="onboarding_join_existing">Bestehendem Zuhause beitreten</string>
|
||||||
|
<string name="onboarding_already_have_account">Hast du schon ein Konto? Anmelden</string>
|
||||||
|
<string name="onboarding_skip">Überspringen</string>
|
||||||
|
<string name="onboarding_continue">Weiter</string>
|
||||||
|
<string name="onboarding_get_started">Loslegen</string>
|
||||||
|
<string name="onboarding_feature_tasks_title">Vergiss nie eine Aufgabe</string>
|
||||||
|
<string name="onboarding_feature_tasks_desc">Verwalte alle Aufgaben zur Hauspflege an einem Ort mit smarten Erinnerungen</string>
|
||||||
|
<string name="onboarding_feature_docs_title">Dokumente immer griffbereit</string>
|
||||||
|
<string name="onboarding_feature_docs_desc">Garantien, Anleitungen und Belege sicher speichern und jederzeit abrufen</string>
|
||||||
|
<string name="onboarding_feature_contractors_title">Deine vertrauten Dienstleister</string>
|
||||||
|
<string name="onboarding_feature_contractors_desc">Halte alle Dienstleisterkontakte organisiert und schnell griffbereit</string>
|
||||||
|
<string name="onboarding_feature_family_title">Mit der Familie teilen</string>
|
||||||
|
<string name="onboarding_feature_family_desc">Lade Familienmitglieder ein, um gemeinsam an der Hauspflege zu arbeiten</string>
|
||||||
|
<string name="onboarding_feature_notifications_title">Smarte Benachrichtigungen</string>
|
||||||
|
<string name="onboarding_feature_notifications_desc">Erhalte aktive Erinnerungen, mit denen du Aufgaben direkt aus der Benachrichtigung abschließt</string>
|
||||||
|
<string name="onboarding_feature_widgets_title">Startbildschirm-Widgets</string>
|
||||||
|
<string name="onboarding_feature_widgets_desc">Schneller Zugriff auf Aufgaben und Erinnerungen direkt vom Startbildschirm</string>
|
||||||
|
<string name="onboarding_location_title">Wo ist dein Zuhause?</string>
|
||||||
|
<string name="onboarding_location_subtitle">Wir schlagen Pflegeaufgaben passend zum Klima deiner Region vor</string>
|
||||||
|
<string name="onboarding_location_use_my_location">Meinen Standort verwenden</string>
|
||||||
|
<string name="onboarding_location_detecting">Wird ermittelt...</string>
|
||||||
|
<string name="onboarding_location_enter_zip">Stattdessen Postleitzahl eingeben</string>
|
||||||
|
<string name="onboarding_location_enter_zip_prompt">Gib deine Postleitzahl ein</string>
|
||||||
|
<string name="onboarding_location_zip_placeholder">Postleitzahl</string>
|
||||||
|
<string name="onboarding_name_residence_title">Benenne dein Zuhause</string>
|
||||||
|
<string name="onboarding_name_residence_subtitle">Gib deinem Objekt einen Namen zur leichteren Erkennung</string>
|
||||||
|
<string name="onboarding_name_residence_placeholder">z. B. Mein Zuhause, Strandhaus, Wohnung</string>
|
||||||
|
<string name="onboarding_name_residence_hint">Du kannst später weitere Details hinzufügen</string>
|
||||||
|
<string name="onboarding_create_account_title">Dein Zuhause sichern</string>
|
||||||
|
<string name="onboarding_create_account_subtitle">Erstelle ein Konto, um geräteübergreifend zu synchronisieren</string>
|
||||||
|
<string name="onboarding_create_with_email">Konto mit E-Mail erstellen</string>
|
||||||
|
<string name="onboarding_verify_email_title">E-Mail bestätigen</string>
|
||||||
|
<string name="onboarding_verify_email_subtitle">Wir haben einen 6-stelligen Code an deine E-Mail gesendet. Gib ihn unten ein, um dein Konto zu bestätigen.</string>
|
||||||
|
<string name="onboarding_verify_email_hint">Keinen Code erhalten? Prüfe deinen Spam-Ordner</string>
|
||||||
|
<string name="onboarding_join_title">Einem Wohnsitz beitreten</string>
|
||||||
|
<string name="onboarding_join_subtitle">Gib den 6-stelligen Code ein, der mit dir geteilt wurde, um einem bestehenden Zuhause beizutreten</string>
|
||||||
|
<string name="onboarding_join_placeholder">Teilcode eingeben</string>
|
||||||
|
<string name="onboarding_join_button">Wohnsitz beitreten</string>
|
||||||
|
<string name="onboarding_tasks_title">Alles bereit!</string>
|
||||||
|
<string name="onboarding_tasks_subtitle">Lass uns mit ein paar Aufgaben starten. Je mehr du auswählst, desto besser helfen wir dir beim Erinnern!</string>
|
||||||
|
<string name="onboarding_tasks_selected">%1$d/%2$d Aufgaben ausgewählt</string>
|
||||||
|
<string name="onboarding_tasks_add_popular">Beliebteste hinzufügen</string>
|
||||||
|
<string name="onboarding_tasks_continue">%1$d Aufgaben hinzufügen & weiter</string>
|
||||||
|
<string name="onboarding_tasks_skip">Vorerst überspringen</string>
|
||||||
|
<string name="onboarding_category_hvac">Klima & Heizung</string>
|
||||||
|
<string name="onboarding_category_safety">Sicherheit</string>
|
||||||
|
<string name="onboarding_category_plumbing">Sanitär</string>
|
||||||
|
<string name="onboarding_category_outdoor">Außenbereich & Garten</string>
|
||||||
|
<string name="onboarding_category_appliances">Geräte</string>
|
||||||
|
<string name="onboarding_category_general">Allgemein</string>
|
||||||
|
<string name="onboarding_subscription_title">Auf Pro upgraden</string>
|
||||||
|
<string name="onboarding_subscription_subtitle">Bring dein Hausmanagement auf die nächste Stufe</string>
|
||||||
|
<string name="onboarding_subscription_pro">HONEYDUE PRO</string>
|
||||||
|
<string name="onboarding_subscription_social_proof">4,9 • 10.000+ Hausbesitzer</string>
|
||||||
|
<string name="onboarding_subscription_benefit_properties">Unbegrenzte Objekte</string>
|
||||||
|
<string name="onboarding_subscription_benefit_properties_desc">Verwalte jedes Haus, das dir gehört</string>
|
||||||
|
<string name="onboarding_subscription_benefit_reminders">Smarte Erinnerungen</string>
|
||||||
|
<string name="onboarding_subscription_benefit_reminders_desc">Verpasse nie eine Pflegefrist</string>
|
||||||
|
<string name="onboarding_subscription_benefit_documents">Dokumententresor</string>
|
||||||
|
<string name="onboarding_subscription_benefit_documents_desc">Alle deine Dokumente an einem Ort</string>
|
||||||
|
<string name="onboarding_subscription_benefit_family">Familienfreigabe</string>
|
||||||
|
<string name="onboarding_subscription_benefit_family_desc">Bring alle auf den gleichen Stand</string>
|
||||||
|
<string name="onboarding_subscription_benefit_insights">Ausgaben-Einblicke</string>
|
||||||
|
<string name="onboarding_subscription_benefit_insights_desc">Sieh, wofür dein Geld draufgeht</string>
|
||||||
|
<string name="onboarding_subscription_benefit_contractor_sharing">Dienstleister teilen</string>
|
||||||
|
<string name="onboarding_subscription_benefit_contractor_sharing_desc">Teile deine vertrauten Dienstleister mit Familie und Freunden</string>
|
||||||
|
<string name="onboarding_subscription_benefit_widgets">Startbildschirm-Widgets</string>
|
||||||
|
<string name="onboarding_subscription_benefit_widgets_desc">Schnellaktionen direkt vom Startbildschirm</string>
|
||||||
|
<string name="onboarding_subscription_benefit_notifications">Aktive Benachrichtigungen</string>
|
||||||
|
<string name="onboarding_subscription_benefit_notifications_desc">Aufgaben direkt aus Benachrichtigungen abschließen</string>
|
||||||
|
<string name="onboarding_subscription_choose_plan">Wähle deinen Tarif</string>
|
||||||
|
<string name="onboarding_subscription_monthly">Monatlich</string>
|
||||||
|
<string name="onboarding_subscription_yearly">Jährlich</string>
|
||||||
|
<string name="onboarding_subscription_save">30 % sparen</string>
|
||||||
|
<string name="onboarding_subscription_monthly_price">2,99 $/Monat</string>
|
||||||
|
<string name="onboarding_subscription_yearly_price">23,99 $/Jahr</string>
|
||||||
|
<string name="onboarding_subscription_yearly_monthly">Nur 1,99 $/Monat</string>
|
||||||
|
<string name="onboarding_subscription_start_trial">7 Tage kostenlos testen</string>
|
||||||
|
<string name="onboarding_subscription_continue_free">Mit Gratisversion fortfahren</string>
|
||||||
|
<string name="onboarding_subscription_trial_terms">7 Tage kostenlos, danach %1$s. Jederzeit kündbar.</string>
|
||||||
|
<string name="onboarding_home_profile_title">Erzähl uns von deinem Zuhause</string>
|
||||||
|
<string name="onboarding_home_profile_subtitle">Alles optional — hilft uns, deinen Pflegeplan zu personalisieren</string>
|
||||||
|
<string name="onboarding_home_profile_systems">Systeme</string>
|
||||||
|
<string name="onboarding_home_profile_features">Ausstattung</string>
|
||||||
|
<string name="onboarding_home_profile_exterior">Außen</string>
|
||||||
|
<string name="onboarding_home_profile_interior">Innen</string>
|
||||||
|
<string name="for_you_tab">Für dich</string>
|
||||||
|
<string name="browse_tab">Durchsuchen</string>
|
||||||
|
<string name="biometric_lock_title">App gesperrt</string>
|
||||||
|
<string name="biometric_lock_description">Authentifiziere dich, um honeyDue zu entsperren</string>
|
||||||
|
<string name="biometric_lock_setting_title">Biometrische Sperre</string>
|
||||||
|
<string name="biometric_lock_setting_subtitle">Authentifizierung beim Öffnen der App verlangen</string>
|
||||||
|
<string name="biometric_prompt_title">honeyDue entsperren</string>
|
||||||
|
<string name="biometric_prompt_subtitle">Bestätige deine Identität, um fortzufahren</string>
|
||||||
|
<string name="biometric_unlock_button">Mit Biometrie entsperren</string>
|
||||||
|
<string name="biometric_auth_failed">Authentifizierung fehlgeschlagen</string>
|
||||||
|
<string name="biometric_not_available">Biometrische Authentifizierung ist auf diesem Gerät nicht verfügbar</string>
|
||||||
|
<string name="notif_channel_task_reminder_name">Aufgabenerinnerungen</string>
|
||||||
|
<string name="notif_channel_task_reminder_description">Erinnerungen an anstehende und bald fällige Aufgaben</string>
|
||||||
|
<string name="notif_channel_task_overdue_name">Überfällige Aufgaben</string>
|
||||||
|
<string name="notif_channel_task_overdue_description">Hinweise, wenn eine Aufgabe überfällig ist</string>
|
||||||
|
<string name="notif_channel_residence_invite_name">Wohnsitz-Einladungen</string>
|
||||||
|
<string name="notif_channel_residence_invite_description">Einladungen zu einem geteilten Wohnsitz</string>
|
||||||
|
<string name="notif_channel_subscription_name">Abo-Updates</string>
|
||||||
|
<string name="notif_channel_subscription_description">Status- und Abrechnungsupdates zum Abo</string>
|
||||||
|
<string name="common_selected">Ausgewählt</string>
|
||||||
|
<string name="common_open">Öffnen</string>
|
||||||
|
<string name="common_skip">Überspringen</string>
|
||||||
|
<string name="common_or">oder</string>
|
||||||
|
<string name="common_info">Info</string>
|
||||||
|
<string name="common_verified">Verifiziert</string>
|
||||||
|
<string name="common_warning">Warnung</string>
|
||||||
|
<string name="common_photo">Foto</string>
|
||||||
|
<string name="common_included">Enthalten</string>
|
||||||
|
<string name="common_verifying">Wird überprüft…</string>
|
||||||
|
<string name="time_am">AM</string>
|
||||||
|
<string name="time_pm">PM</string>
|
||||||
|
<string name="time_eve">ABEND</string>
|
||||||
|
<string name="error_network_title">Netzwerkfehler</string>
|
||||||
|
<string name="image_failed_to_load">Laden fehlgeschlagen</string>
|
||||||
|
<string name="theme_appearance_title">Darstellung</string>
|
||||||
|
<string name="theme_use_system_colors">Systemfarben verwenden</string>
|
||||||
|
<string name="theme_material_you_desc">Material You von Android 12+ folgen (Hintergrundfarben)</string>
|
||||||
|
<string name="paywall_choose_plan_title">Wähle deinen Tarif</string>
|
||||||
|
<string name="paywall_choose_plan_subtitle">Wechsle zu Pro für unbegrenzten Zugriff</string>
|
||||||
|
<string name="paywall_upgrade_to_pro">Auf Pro upgraden</string>
|
||||||
|
<string name="paywall_col_feature">Funktion</string>
|
||||||
|
<string name="paywall_col_free">Kostenlos</string>
|
||||||
|
<string name="paywall_col_pro">Pro</string>
|
||||||
|
<string name="paywall_feat_properties">Immobilien</string>
|
||||||
|
<string name="paywall_feat_tasks">Aufgaben</string>
|
||||||
|
<string name="paywall_feat_contractors">Auftragnehmer</string>
|
||||||
|
<string name="paywall_feat_documents">Dokumente</string>
|
||||||
|
<string name="paywall_val_1_property">1 Immobilie</string>
|
||||||
|
<string name="paywall_val_10_tasks">10 Aufgaben</string>
|
||||||
|
<string name="paywall_val_unlimited">Unbegrenzt</string>
|
||||||
|
<string name="paywall_val_not_available">Nicht verfügbar</string>
|
||||||
|
<string name="reset_check_email_title">Prüfe deine E-Mails</string>
|
||||||
|
<string name="reset_sent_code_to">Wir haben einen 6-stelligen Code gesendet an</string>
|
||||||
|
<string name="reset_code_expires">Der Code läuft in 15 Minuten ab</string>
|
||||||
|
<string name="reset_enter_code_hint">Gib den 6-stelligen Code aus deiner E-Mail ein</string>
|
||||||
|
<string name="reset_code_verified_msg">Code verifiziert! Lege jetzt dein neues Passwort fest</string>
|
||||||
|
<string name="reset_didnt_receive">Code nicht erhalten?</string>
|
||||||
|
<string name="reset_check_spam">Prüfe deinen Spam-Ordner, falls du ihn nicht siehst</string>
|
||||||
|
<string name="reset_verify_failed_title">Code-Verifizierung fehlgeschlagen</string>
|
||||||
|
<string name="verify_email_required_msg">E-Mail-Verifizierung ist erforderlich. Prüfe deinen Posteingang auf einen 6-stelligen Code.</string>
|
||||||
|
<string name="verify_email_invalid_code">Bitte gib einen gültigen 6-stelligen Code ein</string>
|
||||||
|
<string name="verify_email_didnt_receive">Code nicht erhalten? Prüfe deinen Spam-Ordner oder kontaktiere den Support.</string>
|
||||||
|
<string name="verify_email_failed_title">Verifizierung fehlgeschlagen</string>
|
||||||
|
<string name="reset_pw_success_msg">Dein Passwort wurde erfolgreich zurückgesetzt</string>
|
||||||
|
<string name="reset_pw_can_login">Du kannst dich jetzt mit deinem neuen Passwort anmelden</string>
|
||||||
|
<string name="reset_return_to_login">Zurück zur Anmeldung</string>
|
||||||
|
<string name="reset_set_new_pw_title">Neues Passwort festlegen</string>
|
||||||
|
<string name="reset_create_strong_pw">Erstelle ein sicheres Passwort, um dein Konto zu schützen</string>
|
||||||
|
<string name="reset_pw_requirements">Passwortanforderungen</string>
|
||||||
|
<string name="reset_pw_failed_title">Zurücksetzen des Passworts fehlgeschlagen</string>
|
||||||
|
<string name="forgot_send_code_hint">Wir senden einen 6-stelligen Verifizierungscode an diese Adresse</string>
|
||||||
|
<string name="forgot_check_email_msg">Prüfe deine E-Mails auf einen 6-stelligen Verifizierungscode</string>
|
||||||
|
<string name="forgot_back_to_login">Passwort wieder eingefallen? Zurück zur Anmeldung</string>
|
||||||
|
<string name="forgot_send_failed_title">Senden des Reset-Codes fehlgeschlagen</string>
|
||||||
|
<string name="join_property_title">Immobilie beitreten</string>
|
||||||
|
<string name="join_shared_property_header">Einer geteilten Immobilie beitreten</string>
|
||||||
|
<string name="join_enter_code_desc">Gib den 6-stelligen Freigabecode des Eigentümers ein.</string>
|
||||||
|
<string name="join_share_code_label">Freigabecode</string>
|
||||||
|
<string name="join_share_code_placeholder">ABC123</string>
|
||||||
|
<string name="join_code_helper">Codes bestehen aus 6 Großbuchstaben</string>
|
||||||
|
<string name="join_joining">Beitreten…</string>
|
||||||
|
<string name="biometric_enter_pin">PIN zum Entsperren eingeben</string>
|
||||||
|
<string name="biometric_pin_label">4-stellige PIN</string>
|
||||||
|
<string name="biometric_incorrect_pin">Falsche PIN</string>
|
||||||
|
<string name="biometric_unlock">Entsperren</string>
|
||||||
|
<string name="tasks_create_failed_title">Aufgabe konnte nicht erstellt werden</string>
|
||||||
|
<string name="tasks_all_title">Alle Aufgaben</string>
|
||||||
|
<string name="tasks_add">Aufgabe hinzufügen</string>
|
||||||
|
<string name="tasks_load_failed_title">Aufgaben konnten nicht geladen werden</string>
|
||||||
|
<string name="tasks_all_empty_subtitle">Erstelle deine erste Aufgabe, um loszulegen</string>
|
||||||
|
<string name="tasks_add_property_first">Füge zuerst eine Immobilie über den Tab Residenzen hinzu</string>
|
||||||
|
<string name="residences_upgrade_to_add">Zum Hinzufügen upgraden</string>
|
||||||
|
<string name="residences_primary_cd">Hauptwohnsitz</string>
|
||||||
|
<string name="residence_unit_label">Einheit: %1$s</string>
|
||||||
|
<string name="residence_error_loading_tasks">Fehler beim Laden der Aufgaben: %1$s</string>
|
||||||
|
<string name="residence_error_loading_contractors">Fehler beim Laden der Auftragnehmer: %1$s</string>
|
||||||
|
<string name="profile_subscription_cd">Abonnement</string>
|
||||||
|
<string name="onboarding_sub_cancel_note">Jederzeit in den Einstellungen kündbar • Keine Verpflichtung</string>
|
||||||
|
<string name="onboarding_joining_residence">Residenz wird beigetreten…</string>
|
||||||
|
<string name="onboarding_sub_trial_legal">7 Tage kostenlos testen, danach %1$s</string>
|
||||||
|
<string name="common_collapse">Einklappen</string>
|
||||||
|
<string name="common_expand">Ausklappen</string>
|
||||||
|
<string name="common_not_selected">Nicht ausgewählt</string>
|
||||||
|
<string name="auth_logging_in">Anmeldung läuft…</string>
|
||||||
|
<string name="auth_requirement_met">Anforderung erfüllt</string>
|
||||||
|
<string name="auth_requirement_not_met">Anforderung nicht erfüllt</string>
|
||||||
|
<string name="error_something_wrong">Etwas ist schiefgelaufen</string>
|
||||||
|
<string name="paywall_feat_not_included">Nicht enthalten</string>
|
||||||
|
<string name="completions_contractor_prefix">Auftragnehmer:</string>
|
||||||
|
<string name="completions_completed_by_prefix">Abgeschlossen von:</string>
|
||||||
|
<string name="completions_remove_photo">Foto entfernen</string>
|
||||||
|
<string name="completions_star_rating_cd">%1$d Sterne</string>
|
||||||
|
<string name="photos_completion_title">Abschlussfotos</string>
|
||||||
|
<string name="photos_task_completion_cd">Foto vom Aufgabenabschluss</string>
|
||||||
|
<string name="documents_residence_ref">Residenz Nr. %1$d</string>
|
||||||
|
<string name="documents_task_ref">Aufgabe Nr. %1$d</string>
|
||||||
|
<string name="documents_image_index">Bild %1$d von %2$d</string>
|
||||||
|
<string name="documents_images_title">Dokumentbilder</string>
|
||||||
|
<string name="documents_empty_no_warranties">Keine Garantien gefunden</string>
|
||||||
|
<string name="documents_empty_no_documents">Keine Dokumente gefunden</string>
|
||||||
|
<string name="documents_days_remaining_count">Noch %1$d Tage</string>
|
||||||
|
<string name="contractors_property_ref">Immobilie Nr.</string>
|
||||||
|
<string name="residence_share_failed">Teilen der Residenz fehlgeschlagen</string>
|
||||||
|
<string name="theme_default">Standard</string>
|
||||||
|
<string name="theme_default_desc">Lebendige iOS-Systemfarben</string>
|
||||||
|
<string name="theme_teal">Blaugrün</string>
|
||||||
|
<string name="theme_teal_desc">Blaugrün mit warmen Akzenten</string>
|
||||||
|
<string name="theme_ocean">Ozean</string>
|
||||||
|
<string name="theme_ocean_desc">Tiefe Blautöne und Korallennuancen</string>
|
||||||
|
<string name="theme_forest">Wald</string>
|
||||||
|
<string name="theme_forest_desc">Erdige Grüntöne und goldene Schattierungen</string>
|
||||||
|
<string name="theme_sunset">Sonnenuntergang</string>
|
||||||
|
<string name="theme_sunset_desc">Warme Orange- und Rottöne</string>
|
||||||
|
<string name="theme_monochrome">Monochrom</string>
|
||||||
|
<string name="theme_monochrome_desc">Elegante Graustufen</string>
|
||||||
|
<string name="theme_lavender">Lavendel</string>
|
||||||
|
<string name="theme_lavender_desc">Sanftes Violett mit rosa Akzenten</string>
|
||||||
|
<string name="theme_crimson">Karmesinrot</string>
|
||||||
|
<string name="theme_crimson_desc">Kräftiges Rot mit warmen Glanzlichtern</string>
|
||||||
|
<string name="theme_midnight">Mitternacht</string>
|
||||||
|
<string name="theme_midnight_desc">Tiefes Marineblau mit Himmelblau</string>
|
||||||
|
<string name="theme_desert">Wüste</string>
|
||||||
|
<string name="theme_desert_desc">Warme Terrakotta- und Sandtöne</string>
|
||||||
|
<string name="theme_mint">Minze</string>
|
||||||
|
<string name="theme_mint_desc">Frisches Grün mit Türkis</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -472,4 +472,585 @@
|
|||||||
<string name="subscription_features">Funciones Premium</string>
|
<string name="subscription_features">Funciones Premium</string>
|
||||||
<string name="subscription_limit_properties">Has alcanzado el limite de propiedades de tu plan</string>
|
<string name="subscription_limit_properties">Has alcanzado el limite de propiedades de tu plan</string>
|
||||||
<string name="subscription_limit_tasks">Has alcanzado el limite de tareas de tu plan</string>
|
<string name="subscription_limit_tasks">Has alcanzado el limite de tareas de tu plan</string>
|
||||||
|
<string name="home_profile_heating">Calefacción</string>
|
||||||
|
<string name="home_profile_cooling">Refrigeración</string>
|
||||||
|
<string name="home_profile_water_heater">Calentador de agua</string>
|
||||||
|
<string name="home_profile_pool">Piscina</string>
|
||||||
|
<string name="home_profile_sprinkler_system">Sistema de riego</string>
|
||||||
|
<string name="home_profile_fireplace">Chimenea</string>
|
||||||
|
<string name="home_profile_garage">Garaje</string>
|
||||||
|
<string name="home_profile_basement">Sótano</string>
|
||||||
|
<string name="home_profile_attic">Ático</string>
|
||||||
|
<string name="home_profile_septic">Fosa séptica</string>
|
||||||
|
<string name="home_profile_roof_type">Tipo de techo</string>
|
||||||
|
<string name="home_profile_exterior">Exterior</string>
|
||||||
|
<string name="home_profile_flooring">Suelo</string>
|
||||||
|
<string name="home_profile_landscaping">Jardinería</string>
|
||||||
|
<string name="onboarding_first_task_selected_count">%1$d tareas seleccionadas</string>
|
||||||
|
<string name="onboarding_first_task_add_continue">Añadir %1$d tareas y continuar</string>
|
||||||
|
<string name="onboarding_first_task_finding">Buscando tareas para tu hogar...</string>
|
||||||
|
<string name="onboarding_first_task_no_suggestions">Aún no hay sugerencias personalizadas: explora el catálogo completo u omite este paso.</string>
|
||||||
|
<string name="onboarding_first_task_browse_all">Ver todo</string>
|
||||||
|
<string name="onboarding_first_task_skip">Omitir</string>
|
||||||
|
<string name="onboarding_first_task_suggestions_error">No se pudieron cargar tus sugerencias</string>
|
||||||
|
<string name="onboarding_first_task_connection_error">Revisa tu conexión e inténtalo de nuevo.</string>
|
||||||
|
<string name="onboarding_first_task_loading_catalog">Cargando el catálogo de tareas...</string>
|
||||||
|
<string name="onboarding_first_task_catalog_error">No se pudo cargar el catálogo de tareas</string>
|
||||||
|
<string name="onboarding_first_task_no_templates">No hay plantillas disponibles ahora mismo.</string>
|
||||||
|
<string name="onboarding_first_task_selected">Seleccionado</string>
|
||||||
|
<string name="onboarding_first_task_offline">Sin conexión</string>
|
||||||
|
<string name="onboarding_first_task_skip_for_now">Omitir por ahora</string>
|
||||||
|
<string name="upgrade_hero_title">Mejora a Pro</string>
|
||||||
|
<string name="upgrade_hero_subtitle">Aprovecha todo el potencial de honeyDue</string>
|
||||||
|
<string name="upgrade_choose_plan">Elige tu plan</string>
|
||||||
|
<string name="upgrade_plan_yearly">Anual</string>
|
||||||
|
<string name="upgrade_plan_monthly">Mensual</string>
|
||||||
|
<string name="upgrade_plan_save_50">Ahorra 50%</string>
|
||||||
|
<string name="upgrade_best_value">MEJOR VALOR</string>
|
||||||
|
<string name="upgrade_whats_included">Qué incluye</string>
|
||||||
|
<string name="upgrade_feature_unlimited_properties">Propiedades ilimitadas</string>
|
||||||
|
<string name="upgrade_feature_unlimited_properties_desc">Gestiona el mantenimiento de todos tus hogares</string>
|
||||||
|
<string name="upgrade_feature_unlimited_tasks">Tareas ilimitadas</string>
|
||||||
|
<string name="upgrade_feature_unlimited_tasks_desc">No vuelvas a olvidar una tarea de mantenimiento</string>
|
||||||
|
<string name="upgrade_feature_contractor_management">Gestión de contratistas</string>
|
||||||
|
<string name="upgrade_feature_contractor_management_desc">Guarda y califica a tus contratistas de confianza</string>
|
||||||
|
<string name="upgrade_feature_document_vault">Bóveda de documentos</string>
|
||||||
|
<string name="upgrade_feature_document_vault_desc">Guarda garantías, recibos y manuales</string>
|
||||||
|
<string name="upgrade_feature_family_sharing">Uso compartido en familia</string>
|
||||||
|
<string name="upgrade_feature_family_sharing_desc">Invita a familiares a colaborar</string>
|
||||||
|
<string name="upgrade_feature_smart_reminders">Recordatorios inteligentes</string>
|
||||||
|
<string name="upgrade_feature_smart_reminders_desc">Recibe avisos cuando venzan las tareas</string>
|
||||||
|
<string name="upgrade_feature_document_warranty_storage">Almacenamiento de documentos y garantías</string>
|
||||||
|
<string name="upgrade_subscribe_now">Suscríbete ahora</string>
|
||||||
|
<string name="upgrade_restore_purchases">Restaurar compras</string>
|
||||||
|
<string name="upgrade_terms_text">La suscripción se renueva automáticamente a menos que se cancele al menos 24 horas antes del final del período actual. Gestiona las suscripciones en los ajustes de tu dispositivo.</string>
|
||||||
|
<string name="upgrade_terms_of_use">Términos de uso</string>
|
||||||
|
<string name="upgrade_included">Incluido</string>
|
||||||
|
<string name="upgrade_compare_free_vs_pro">Compara Gratis vs Pro</string>
|
||||||
|
<string name="upgrade_maybe_later">Quizás más tarde</string>
|
||||||
|
<string name="upgrade_warning">Advertencia</string>
|
||||||
|
<string name="upgrade_subscription_active">Suscripción activa</string>
|
||||||
|
<string name="upgrade_subscription_active_message">¡Ya tienes acceso completo a todas las funciones Pro!</string>
|
||||||
|
<string name="upgrade_feature_required_title">Mejora requerida</string>
|
||||||
|
<string name="upgrade_feature_required_message">Esta función está disponible con una suscripción Pro.</string>
|
||||||
|
<string name="upgrade_prompt_default_message">Desbloquea acceso ilimitado a todas las funciones</string>
|
||||||
|
<string name="upgrade_product_monthly_name">honeyDue Pro Mensual</string>
|
||||||
|
<string name="upgrade_product_annual_name">honeyDue Pro Anual</string>
|
||||||
|
<string name="upgrade_billed_monthly">Facturación mensual</string>
|
||||||
|
<string name="upgrade_billed_annually">Facturación anual</string>
|
||||||
|
<string name="upgrade_save_22">Ahorra 22%</string>
|
||||||
|
<string name="tasks_column_done">Hechas</string>
|
||||||
|
<string name="tasks_column_archived">Archivadas</string>
|
||||||
|
<string name="tasks_column_empty">Sin tareas</string>
|
||||||
|
<string name="tasks_new_title">Nueva tarea</string>
|
||||||
|
<string name="tasks_title_field_label">Título</string>
|
||||||
|
<string name="tasks_title_placeholder">p. ej. Vaciar el calentador de agua</string>
|
||||||
|
<string name="tasks_description_placeholder">Detalles opcionales</string>
|
||||||
|
<string name="tasks_due_date_optional_label">Fecha de vencimiento (opcional)</string>
|
||||||
|
<string name="tasks_due_date_placeholder_format">aaaa-MM-dd</string>
|
||||||
|
<string name="tasks_due_date_blank_helper">Déjalo en blanco si no hay fecha de vencimiento</string>
|
||||||
|
<string name="tasks_estimated_cost_optional_label">Costo estimado (opcional)</string>
|
||||||
|
<string name="suggestions_title">Tareas sugeridas</string>
|
||||||
|
<string name="suggestions_skip">Omitir</string>
|
||||||
|
<string name="suggestions_accept">Aceptar</string>
|
||||||
|
<string name="suggestions_load_failed">No se pudieron cargar las sugerencias</string>
|
||||||
|
<string name="suggestions_empty_title">Aún no hay sugerencias</string>
|
||||||
|
<string name="suggestions_empty_subtitle">Completa el perfil de tu hogar para ver recomendaciones personalizadas.</string>
|
||||||
|
<string name="completion_history_title">Historial de finalizaciones</string>
|
||||||
|
<string name="completion_history_count_one">%1$d finalización</string>
|
||||||
|
<string name="completion_history_count_other">%1$d finalizaciones</string>
|
||||||
|
<string name="completion_history_loading">Cargando finalizaciones...</string>
|
||||||
|
<string name="completion_history_load_failed">No se pudieron cargar las finalizaciones</string>
|
||||||
|
<string name="completion_history_empty_title">Aún no hay finalizaciones</string>
|
||||||
|
<string name="completion_history_empty_message">Esta tarea no se ha completado.</string>
|
||||||
|
<string name="completion_history_completed_by">Completada por %1$s</string>
|
||||||
|
<string name="completion_history_view_photo">Ver foto</string>
|
||||||
|
<string name="manage_users_remove_user">Eliminar usuario</string>
|
||||||
|
<string name="manage_users_copy_code">Copiar código</string>
|
||||||
|
<string name="manage_users_code_copied">Código copiado al portapapeles</string>
|
||||||
|
<string name="manage_users_load_failed">No se pudieron cargar los usuarios</string>
|
||||||
|
<string name="manage_users_remove_confirm">¿Eliminar a %1$s de esta propiedad?</string>
|
||||||
|
<string name="properties_shared_users_count">Usuarios compartidos (%1$d)</string>
|
||||||
|
<string name="properties_no_shared_users">Sin usuarios compartidos</string>
|
||||||
|
<string name="properties_shared_users_helper">Usuarios con acceso a esta residencia. Usa el botón de compartir para invitar a otros.</string>
|
||||||
|
<string name="properties_remove_user_confirm">¿Seguro que quieres eliminar a %1$s de esta residencia?</string>
|
||||||
|
<string name="properties_remove_button">Eliminar</string>
|
||||||
|
<string name="documents_expires_label">Vence</string>
|
||||||
|
<string name="auth_password_requirement_length">Al menos 8 caracteres</string>
|
||||||
|
<string name="auth_password_requirement_uppercase">Contiene una letra mayúscula</string>
|
||||||
|
<string name="auth_password_requirement_lowercase">Contiene una letra minúscula</string>
|
||||||
|
<string name="auth_password_requirement_digit">Contiene un número</string>
|
||||||
|
<string name="auth_password_requirement_match">Las contraseñas coinciden</string>
|
||||||
|
<string name="auth_password_requirements_title">Requisitos de la contraseña</string>
|
||||||
|
<string name="auth_password_complexity_error">La contraseña debe tener al menos 8 caracteres con al menos una mayúscula, una minúscula y un número</string>
|
||||||
|
<string name="properties_join_residence_title">Unirse a la residencia</string>
|
||||||
|
<string name="properties_join_residence_message">¿Quieres unirte a esta residencia compartida?</string>
|
||||||
|
<string name="properties_join_success">Te uniste a la residencia</string>
|
||||||
|
<string name="properties_join_success_message">Ahora tienes acceso a %1$s.</string>
|
||||||
|
<string name="properties_join_failed">Error al unirse</string>
|
||||||
|
<string name="properties_joining">Uniéndose...</string>
|
||||||
|
<string name="properties_shared_by">Compartido por: %1$s</string>
|
||||||
|
<string name="properties_expires">Expira: %1$s</string>
|
||||||
|
<string name="properties_share_upgrade_title">Función Pro</string>
|
||||||
|
<string name="properties_share_upgrade_message">Compartir residencias es una función Pro. Mejora a Pro para invitar a tu familia a colaborar en el mantenimiento del hogar.</string>
|
||||||
|
<string name="tasks_failed_to_cancel">No se pudo cancelar la tarea</string>
|
||||||
|
<string name="tasks_failed_to_restore">No se pudo restaurar la tarea</string>
|
||||||
|
<string name="tasks_failed_to_mark_in_progress">No se pudo marcar la tarea en curso</string>
|
||||||
|
<string name="tasks_failed_to_archive">No se pudo archivar la tarea</string>
|
||||||
|
<string name="tasks_failed_to_unarchive">No se pudo desarchivar la tarea</string>
|
||||||
|
<string name="tasks_card_in_progress">EN CURSO</string>
|
||||||
|
<string name="tasks_card_actions">Acciones</string>
|
||||||
|
<string name="tasks_card_mark_in_progress">Marcar en curso</string>
|
||||||
|
<string name="tasks_card_complete_task">Completar tarea</string>
|
||||||
|
<string name="tasks_card_edit_task">Editar tarea</string>
|
||||||
|
<string name="tasks_card_cancel_task">Cancelar tarea</string>
|
||||||
|
<string name="tasks_card_restore_task">Restaurar tarea</string>
|
||||||
|
<string name="tasks_card_archive_task">Archivar tarea</string>
|
||||||
|
<string name="tasks_card_unarchive_task">Desarchivar tarea</string>
|
||||||
|
<string name="tasks_card_not_available">N/D</string>
|
||||||
|
<string name="tasks_card_completed_by">Por: %1$s</string>
|
||||||
|
<string name="tasks_card_cost">Costo: $%1$s</string>
|
||||||
|
<string name="tasks_card_view_photos">Ver fotos (%1$d)</string>
|
||||||
|
<string name="tasks_add_new">Agregar tarea</string>
|
||||||
|
<string name="tasks_property_required">Propiedad *</string>
|
||||||
|
<string name="tasks_property_error">La propiedad es obligatoria</string>
|
||||||
|
<string name="tasks_browse_templates">Explorar plantillas de tareas</string>
|
||||||
|
<string name="tasks_common_tasks">%1$d tareas comunes</string>
|
||||||
|
<string name="tasks_category_error">La categoría es obligatoria</string>
|
||||||
|
<string name="tasks_interval_days">Días de intervalo</string>
|
||||||
|
<string name="tasks_interval_override">Sustituir el intervalo de frecuencia predeterminado</string>
|
||||||
|
<string name="tasks_custom_interval_help">Número de días entre cada repetición</string>
|
||||||
|
<string name="tasks_due_date_format_error">La fecha de vencimiento es obligatoria (formato: AAAA-MM-DD)</string>
|
||||||
|
<string name="tasks_due_date_format">Formato: AAAA-MM-DD</string>
|
||||||
|
<string name="tasks_create">Crear tarea</string>
|
||||||
|
<string name="tasks_in_progress_label">En curso</string>
|
||||||
|
<string name="templates_title">Plantillas de tareas</string>
|
||||||
|
<string name="templates_done">Listo</string>
|
||||||
|
<string name="templates_search_placeholder">Buscar plantillas...</string>
|
||||||
|
<string name="templates_clear">Borrar</string>
|
||||||
|
<string name="templates_result">resultado</string>
|
||||||
|
<string name="templates_results">resultados</string>
|
||||||
|
<string name="templates_no_results_title">No se encontraron plantillas</string>
|
||||||
|
<string name="templates_no_results_message">Prueba con otro término de búsqueda</string>
|
||||||
|
<string name="templates_empty_title">No hay plantillas disponibles</string>
|
||||||
|
<string name="templates_empty_message">Las plantillas aparecerán aquí una vez cargadas</string>
|
||||||
|
<string name="templates_expand">Expandir</string>
|
||||||
|
<string name="templates_collapse">Contraer</string>
|
||||||
|
<string name="templates_add">Agregar</string>
|
||||||
|
<string name="templates_all_categories">Todas</string>
|
||||||
|
<string name="templates_apply">Aplicar</string>
|
||||||
|
<string name="templates_apply_count">Aplicar (%1$d)</string>
|
||||||
|
<string name="templates_selected_count">%1$d seleccionadas</string>
|
||||||
|
<string name="templates_retry">Reintentar</string>
|
||||||
|
<string name="templates_load_failed">No se pudieron cargar las plantillas</string>
|
||||||
|
<string name="templates_create_failed">No se pudieron crear las tareas</string>
|
||||||
|
<string name="completions_complete_task_title">Completar tarea: %1$s</string>
|
||||||
|
<string name="completions_select_contractor">Seleccionar contratista (opcional)</string>
|
||||||
|
<string name="completions_choose_contractor_placeholder">Elige un contratista o déjalo en blanco</string>
|
||||||
|
<string name="completions_expand">Expandir</string>
|
||||||
|
<string name="completions_none_manual">Ninguno (entrada manual)</string>
|
||||||
|
<string name="completions_loading_contractors">Cargando contratistas...</string>
|
||||||
|
<string name="completions_error_loading_contractors">Error al cargar contratistas</string>
|
||||||
|
<string name="completions_completed_by_name">Completado por (opcional)</string>
|
||||||
|
<string name="completions_completed_by_placeholder">Ingresa un nombre si no usas el contratista de arriba</string>
|
||||||
|
<string name="completions_actual_cost_optional">Costo real (opcional)</string>
|
||||||
|
<string name="completions_notes_optional">Notas (opcional)</string>
|
||||||
|
<string name="completions_rating">Calificación: %1$d de 5</string>
|
||||||
|
<string name="completions_add_images">Agregar imágenes</string>
|
||||||
|
<string name="completions_take_photo">Tomar foto</string>
|
||||||
|
<string name="completions_choose_from_library">Elegir de la galería</string>
|
||||||
|
<string name="completions_images_selected">%1$d imagen(es) seleccionada(s)</string>
|
||||||
|
<string name="completions_remove_image">Quitar imagen</string>
|
||||||
|
<string name="completions_complete_button">Completar</string>
|
||||||
|
<string name="completions_quality_rating">Calificación de calidad</string>
|
||||||
|
<string name="completions_photos_count">Fotos (%1$d/%2$d)</string>
|
||||||
|
<string name="completions_camera">Cámara</string>
|
||||||
|
<string name="completions_library">Galería</string>
|
||||||
|
<string name="completions_add_photos_helper">Agrega fotos del trabajo terminado (opcional)</string>
|
||||||
|
<string name="completions_contractor_helper">Vincula esta tarea completada a un contratista</string>
|
||||||
|
<string name="completions_details_section">Detalles de finalización</string>
|
||||||
|
<string name="completions_optional_info">Todos los campos son opcionales</string>
|
||||||
|
<string name="completions_notes_helper">Agrega notas sobre el trabajo terminado</string>
|
||||||
|
<string name="completions_notes_placeholder">Describe el trabajo hecho, problemas encontrados, etc.</string>
|
||||||
|
<string name="completions_rate_quality">Califica la calidad del trabajo realizado</string>
|
||||||
|
<string name="completions_enter_manually">Ingresa el nombre manualmente abajo</string>
|
||||||
|
<string name="manage_users_title">Gestionar usuarios</string>
|
||||||
|
<string name="manage_users_invite_title">Invitar a otros</string>
|
||||||
|
<string name="manage_users_easy_share">Compartir fácil</string>
|
||||||
|
<string name="manage_users_send_invite">Enviar enlace de invitación</string>
|
||||||
|
<string name="manage_users_easy_share_desc">Envía un archivo .honeydue por Mensajes, correo o compartir. Solo tienen que tocar para unirse.</string>
|
||||||
|
<string name="manage_users_share_code">Compartir código</string>
|
||||||
|
<string name="manage_users_no_code">No hay código activo</string>
|
||||||
|
<string name="manage_users_generate">Generar código</string>
|
||||||
|
<string name="manage_users_generate_new">Generar nuevo código</string>
|
||||||
|
<string name="manage_users_code_desc">Comparte este código de 6 caracteres. Pueden ingresarlo en la app para unirse.</string>
|
||||||
|
<string name="manage_users_users_count">Usuarios (%1$d)</string>
|
||||||
|
<string name="manage_users_owner_badge">Propietario</string>
|
||||||
|
<string name="manage_users_remove">Quitar</string>
|
||||||
|
<string name="manage_users_or">o</string>
|
||||||
|
<string name="contractors_share">Compartir contratista</string>
|
||||||
|
<string name="contractors_share_upgrade_title">Función Pro</string>
|
||||||
|
<string name="contractors_share_upgrade_message">Compartir contratistas es una función Pro. Mejora a Pro para compartir tus contratistas de confianza con amigos y familia.</string>
|
||||||
|
<string name="contractors_import_title">Importar contratista</string>
|
||||||
|
<string name="contractors_import_message">¿Quieres importar este contratista?</string>
|
||||||
|
<string name="contractors_import_success">Contratista importado</string>
|
||||||
|
<string name="contractors_import_success_message">%1$s se agregó a tus contactos.</string>
|
||||||
|
<string name="contractors_import_failed">Error al importar</string>
|
||||||
|
<string name="contractors_shared_by">Compartido por: %1$s</string>
|
||||||
|
<string name="contractors_form_add_title">Agregar contratista</string>
|
||||||
|
<string name="contractors_form_edit_title">Editar contratista</string>
|
||||||
|
<string name="contractors_form_basic_info">Información básica</string>
|
||||||
|
<string name="contractors_form_name_required">Nombre *</string>
|
||||||
|
<string name="contractors_form_company">Empresa</string>
|
||||||
|
<string name="contractors_form_residence_optional">Residencia (opcional)</string>
|
||||||
|
<string name="contractors_form_personal_no_residence">Personal (sin residencia)</string>
|
||||||
|
<string name="contractors_form_personal_visibility">Solo tú verás este contratista</string>
|
||||||
|
<string name="contractors_form_shared_visibility">Todos los usuarios de %1$s verán este contratista</string>
|
||||||
|
<string name="contractors_form_contact_info">Información de contacto</string>
|
||||||
|
<string name="contractors_form_phone">Teléfono</string>
|
||||||
|
<string name="contractors_form_email">Correo</string>
|
||||||
|
<string name="contractors_form_website">Sitio web</string>
|
||||||
|
<string name="contractors_form_specialties">Especialidades</string>
|
||||||
|
<string name="contractors_form_address_section">Dirección</string>
|
||||||
|
<string name="contractors_form_street_address">Calle</string>
|
||||||
|
<string name="contractors_form_city">Ciudad</string>
|
||||||
|
<string name="contractors_form_state">Estado</string>
|
||||||
|
<string name="contractors_form_zip_code">Código postal</string>
|
||||||
|
<string name="contractors_form_notes_section">Notas</string>
|
||||||
|
<string name="contractors_form_private_notes">Notas privadas</string>
|
||||||
|
<string name="contractors_form_mark_favorite">Marcar como favorito</string>
|
||||||
|
<string name="contractors_form_add_button">Agregar</string>
|
||||||
|
<string name="contractors_form_save_button">Guardar</string>
|
||||||
|
<string name="documents_form_edit_warranty">Editar garantía</string>
|
||||||
|
<string name="documents_form_edit_document">Editar documento</string>
|
||||||
|
<string name="documents_form_add_warranty">Agregar garantía</string>
|
||||||
|
<string name="documents_form_add_document">Agregar documento</string>
|
||||||
|
<string name="documents_form_select_residence">Seleccionar residencia</string>
|
||||||
|
<string name="documents_form_residence_required">Residencia *</string>
|
||||||
|
<string name="documents_form_document_type_required">Tipo de documento *</string>
|
||||||
|
<string name="documents_form_title_required">Título *</string>
|
||||||
|
<string name="documents_form_item_name_required">Nombre del artículo *</string>
|
||||||
|
<string name="documents_form_model_number">Número de modelo</string>
|
||||||
|
<string name="documents_form_serial_number">Número de serie</string>
|
||||||
|
<string name="documents_form_provider_required">Proveedor/empresa *</string>
|
||||||
|
<string name="documents_form_provider_contact">Contacto del proveedor</string>
|
||||||
|
<string name="documents_form_claim_phone">Teléfono de reclamos</string>
|
||||||
|
<string name="documents_form_claim_email">Correo de reclamos</string>
|
||||||
|
<string name="documents_form_claim_website">Sitio web de reclamos</string>
|
||||||
|
<string name="documents_form_purchase_date">Fecha de compra (AAAA-MM-DD)</string>
|
||||||
|
<string name="documents_form_warranty_start">Fecha de inicio de la garantía (AAAA-MM-DD)</string>
|
||||||
|
<string name="documents_form_warranty_end_required">Fecha de fin de la garantía (AAAA-MM-DD) *</string>
|
||||||
|
<string name="documents_form_description">Descripción</string>
|
||||||
|
<string name="documents_form_category">Categoría</string>
|
||||||
|
<string name="documents_form_select_category">Seleccionar categoría</string>
|
||||||
|
<string name="documents_form_category_none">Ninguna</string>
|
||||||
|
<string name="documents_form_tags">Etiquetas</string>
|
||||||
|
<string name="documents_form_tags_placeholder">etiqueta1, etiqueta2, etiqueta3</string>
|
||||||
|
<string name="documents_form_notes">Notas</string>
|
||||||
|
<string name="documents_form_active">Activo</string>
|
||||||
|
<string name="documents_form_existing_photos">Fotos existentes (%1$d)</string>
|
||||||
|
<string name="documents_form_new_photos">Fotos nuevas (%1$d/%2$d)</string>
|
||||||
|
<string name="documents_form_photos">Fotos (%1$d/%2$d)</string>
|
||||||
|
<string name="documents_form_camera">Cámara</string>
|
||||||
|
<string name="documents_form_gallery">Galería</string>
|
||||||
|
<string name="documents_form_image_number">Imagen %1$d</string>
|
||||||
|
<string name="documents_form_remove_image">Quitar imagen</string>
|
||||||
|
<string name="documents_form_update_warranty">Actualizar garantía</string>
|
||||||
|
<string name="documents_form_update_document">Actualizar documento</string>
|
||||||
|
<string name="documents_form_select_residence_error">Selecciona una residencia</string>
|
||||||
|
<string name="documents_form_title_error">El título es obligatorio</string>
|
||||||
|
<string name="documents_form_item_name_error">El nombre del artículo es obligatorio para las garantías</string>
|
||||||
|
<string name="documents_form_provider_error">El proveedor es obligatorio para las garantías</string>
|
||||||
|
<string name="documents_form_date_placeholder">2024-01-15</string>
|
||||||
|
<string name="documents_form_date_placeholder_end">2025-01-15</string>
|
||||||
|
<string name="documents_form_failed_to_load_residences">No se pudieron cargar las residencias: %1$s</string>
|
||||||
|
<string name="profile_support">Soporte</string>
|
||||||
|
<string name="profile_contact_support">Contactar soporte</string>
|
||||||
|
<string name="profile_contact_support_subtitle">Obtén ayuda con tu cuenta</string>
|
||||||
|
<string name="profile_upgrade_benefits_title">Desbloquea funciones premium</string>
|
||||||
|
<string name="profile_upgrade_benefits_subtitle">Mejora a Pro para la experiencia completa</string>
|
||||||
|
<string name="profile_benefit_unlimited_properties">Propiedades ilimitadas</string>
|
||||||
|
<string name="profile_benefit_document_vault">Almacenamiento de documentos y garantías</string>
|
||||||
|
<string name="profile_benefit_residence_sharing">Compartir residencias</string>
|
||||||
|
<string name="profile_benefit_contractor_sharing">Compartir contratistas</string>
|
||||||
|
<string name="profile_benefit_actionable_notifications">Notificaciones con acciones</string>
|
||||||
|
<string name="profile_benefit_widgets">Widgets de pantalla de inicio</string>
|
||||||
|
<string name="profile_privacy">Política de privacidad</string>
|
||||||
|
<string name="profile_privacy_subtitle">Ver nuestra política de privacidad</string>
|
||||||
|
<string name="profile_app_version">Versión %1$s</string>
|
||||||
|
<string name="profile_app_name">honeyDue</string>
|
||||||
|
<string name="profile_edit_profile">Editar perfil</string>
|
||||||
|
<string name="delete_account_title">Eliminar cuenta</string>
|
||||||
|
<string name="delete_account_subtitle">Elimina tu cuenta de forma permanente</string>
|
||||||
|
<string name="delete_account_warning">Esta acción es permanente y no se puede deshacer. Todos tus datos se eliminarán.</string>
|
||||||
|
<string name="delete_account_shared_warning">Cualquier residencia que poseas y esté compartida con otros usuarios también se eliminará.</string>
|
||||||
|
<string name="delete_account_confirm_password">Ingresa tu contraseña para confirmar</string>
|
||||||
|
<string name="delete_account_confirm_type">Escribe DELETE para confirmar</string>
|
||||||
|
<string name="delete_account_button">Eliminar mi cuenta</string>
|
||||||
|
<string name="delete_account_cancel">Cancelar</string>
|
||||||
|
<string name="delete_account_success">Cuenta eliminada correctamente</string>
|
||||||
|
<string name="delete_account_failed">No se pudo eliminar la cuenta</string>
|
||||||
|
<string name="notifications_daily_digest">Resumen diario</string>
|
||||||
|
<string name="notifications_daily_digest_desc">Resumen diario de tareas pendientes y vencidas</string>
|
||||||
|
<string name="notifications_email_section">Notificaciones por correo</string>
|
||||||
|
<string name="notifications_email_task_completed">Correo de tarea completada</string>
|
||||||
|
<string name="notifications_email_task_completed_desc">Recibe un correo cuando se completa una tarea</string>
|
||||||
|
<string name="notifications_set_custom_time">Definir hora personalizada</string>
|
||||||
|
<string name="notifications_change_time">Cambiar</string>
|
||||||
|
<string name="notifications_select_time">Seleccionar hora de notificación</string>
|
||||||
|
<string name="notifications_master_title">Todas las notificaciones</string>
|
||||||
|
<string name="notifications_master_desc">Activa o desactiva todas las categorías con un toque</string>
|
||||||
|
<string name="notifications_categories_section">Categorías</string>
|
||||||
|
<string name="notifications_category_task_reminder">Recordatorios de tareas</string>
|
||||||
|
<string name="notifications_category_task_reminder_desc">Recordatorios próximos y de vencimiento cercano</string>
|
||||||
|
<string name="notifications_category_task_overdue">Tareas vencidas</string>
|
||||||
|
<string name="notifications_category_task_overdue_desc">Alertas cuando una tarea pasa su fecha de vencimiento</string>
|
||||||
|
<string name="notifications_category_residence_invite">Invitaciones a residencias</string>
|
||||||
|
<string name="notifications_category_residence_invite_desc">Invitaciones para unirse a una residencia compartida</string>
|
||||||
|
<string name="notifications_category_subscription">Actualizaciones de suscripción</string>
|
||||||
|
<string name="notifications_category_subscription_desc">Cambios de facturación y estado del plan</string>
|
||||||
|
<string name="notifications_open_system_settings">Abrir ajustes del sistema</string>
|
||||||
|
<string name="notifications_system_settings_desc">Ajusta sonidos, insignias y el comportamiento de No molestar en los ajustes de Android</string>
|
||||||
|
<string name="common_share">Compartir</string>
|
||||||
|
<string name="common_import">Importar</string>
|
||||||
|
<string name="common_importing">Importando...</string>
|
||||||
|
<string name="common_try_again">Reintentar</string>
|
||||||
|
<string name="home_overdue">Vencidas</string>
|
||||||
|
<string name="home_due_this_week">Vencen esta semana</string>
|
||||||
|
<string name="home_next_30_days">Próximos 30 días</string>
|
||||||
|
<string name="home_your_properties">Tus propiedades</string>
|
||||||
|
<string name="onboarding_welcome_title">Te damos la bienvenida a honeyDue</string>
|
||||||
|
<string name="onboarding_welcome_subtitle">Tu compañero de mantenimiento del hogar</string>
|
||||||
|
<string name="onboarding_start_fresh">Empezar de cero</string>
|
||||||
|
<string name="onboarding_join_existing">Unirte a un hogar existente</string>
|
||||||
|
<string name="onboarding_already_have_account">¿Ya tienes una cuenta? Inicia sesión</string>
|
||||||
|
<string name="onboarding_skip">Omitir</string>
|
||||||
|
<string name="onboarding_continue">Continuar</string>
|
||||||
|
<string name="onboarding_get_started">Comenzar</string>
|
||||||
|
<string name="onboarding_feature_tasks_title">Nunca olvides una tarea</string>
|
||||||
|
<string name="onboarding_feature_tasks_desc">Lleva el control de todas tus tareas de mantenimiento del hogar en un solo lugar con recordatorios inteligentes</string>
|
||||||
|
<string name="onboarding_feature_docs_title">Documentos al alcance de tu mano</string>
|
||||||
|
<string name="onboarding_feature_docs_desc">Guarda garantías, manuales y recibos de forma segura y accede a ellos cuando quieras</string>
|
||||||
|
<string name="onboarding_feature_contractors_title">Tus contratistas de confianza</string>
|
||||||
|
<string name="onboarding_feature_contractors_desc">Mantén todos tus contactos de contratistas organizados y a la mano</string>
|
||||||
|
<string name="onboarding_feature_family_title">Comparte con tu familia</string>
|
||||||
|
<string name="onboarding_feature_family_desc">Invita a tu familia a colaborar juntos en el mantenimiento del hogar</string>
|
||||||
|
<string name="onboarding_feature_notifications_title">Notificaciones inteligentes</string>
|
||||||
|
<string name="onboarding_feature_notifications_desc">Recibe recordatorios con acciones que te permiten completar tareas desde la notificación</string>
|
||||||
|
<string name="onboarding_feature_widgets_title">Widgets de pantalla de inicio</string>
|
||||||
|
<string name="onboarding_feature_widgets_desc">Acceso rápido a tareas y recordatorios directamente desde tu pantalla de inicio</string>
|
||||||
|
<string name="onboarding_location_title">¿Dónde está tu hogar?</string>
|
||||||
|
<string name="onboarding_location_subtitle">Te sugeriremos tareas de mantenimiento según el clima de tu zona</string>
|
||||||
|
<string name="onboarding_location_use_my_location">Usar mi ubicación</string>
|
||||||
|
<string name="onboarding_location_detecting">Detectando...</string>
|
||||||
|
<string name="onboarding_location_enter_zip">Ingresar código postal</string>
|
||||||
|
<string name="onboarding_location_enter_zip_prompt">Ingresa tu código postal</string>
|
||||||
|
<string name="onboarding_location_zip_placeholder">Código postal</string>
|
||||||
|
<string name="onboarding_name_residence_title">Nombra tu hogar</string>
|
||||||
|
<string name="onboarding_name_residence_subtitle">Dale un nombre a tu propiedad para identificarla fácilmente</string>
|
||||||
|
<string name="onboarding_name_residence_placeholder">ej., Mi casa, Casa de playa, Departamento</string>
|
||||||
|
<string name="onboarding_name_residence_hint">Puedes agregar más detalles después</string>
|
||||||
|
<string name="onboarding_create_account_title">Guarda tu hogar</string>
|
||||||
|
<string name="onboarding_create_account_subtitle">Crea una cuenta para sincronizar entre dispositivos</string>
|
||||||
|
<string name="onboarding_create_with_email">Crear cuenta con correo</string>
|
||||||
|
<string name="onboarding_verify_email_title">Verifica tu correo</string>
|
||||||
|
<string name="onboarding_verify_email_subtitle">Enviamos un código de 6 dígitos a tu correo. Ingrésalo abajo para verificar tu cuenta.</string>
|
||||||
|
<string name="onboarding_verify_email_hint">¿No recibiste el código? Revisa tu carpeta de spam</string>
|
||||||
|
<string name="onboarding_join_title">Unirse a una residencia</string>
|
||||||
|
<string name="onboarding_join_subtitle">Ingresa el código de 6 caracteres que compartieron contigo para unirte a un hogar existente</string>
|
||||||
|
<string name="onboarding_join_placeholder">Ingresa el código para compartir</string>
|
||||||
|
<string name="onboarding_join_button">Unirse a la residencia</string>
|
||||||
|
<string name="onboarding_tasks_title">¡Todo listo!</string>
|
||||||
|
<string name="onboarding_tasks_subtitle">Empecemos con algunas tareas. Cuantas más elijas, más te ayudaremos a recordar.</string>
|
||||||
|
<string name="onboarding_tasks_selected">%1$d/%2$d tareas seleccionadas</string>
|
||||||
|
<string name="onboarding_tasks_add_popular">Agregar las más populares</string>
|
||||||
|
<string name="onboarding_tasks_continue">Agregar %1$d tareas y continuar</string>
|
||||||
|
<string name="onboarding_tasks_skip">Omitir por ahora</string>
|
||||||
|
<string name="onboarding_category_hvac">Climatización</string>
|
||||||
|
<string name="onboarding_category_safety">Seguridad</string>
|
||||||
|
<string name="onboarding_category_plumbing">Plomería</string>
|
||||||
|
<string name="onboarding_category_outdoor">Exterior y jardín</string>
|
||||||
|
<string name="onboarding_category_appliances">Electrodomésticos</string>
|
||||||
|
<string name="onboarding_category_general">Hogar general</string>
|
||||||
|
<string name="onboarding_subscription_title">Hazte Pro</string>
|
||||||
|
<string name="onboarding_subscription_subtitle">Lleva la gestión de tu hogar al siguiente nivel</string>
|
||||||
|
<string name="onboarding_subscription_pro">HONEYDUE PRO</string>
|
||||||
|
<string name="onboarding_subscription_social_proof">4.9 • 10 mil+ propietarios</string>
|
||||||
|
<string name="onboarding_subscription_benefit_properties">Propiedades ilimitadas</string>
|
||||||
|
<string name="onboarding_subscription_benefit_properties_desc">Lleva el control de cada hogar que tengas</string>
|
||||||
|
<string name="onboarding_subscription_benefit_reminders">Recordatorios inteligentes</string>
|
||||||
|
<string name="onboarding_subscription_benefit_reminders_desc">No te pierdas ninguna fecha de mantenimiento</string>
|
||||||
|
<string name="onboarding_subscription_benefit_documents">Bóveda de documentos</string>
|
||||||
|
<string name="onboarding_subscription_benefit_documents_desc">Todos tus documentos en un solo lugar</string>
|
||||||
|
<string name="onboarding_subscription_benefit_family">Compartir en familia</string>
|
||||||
|
<string name="onboarding_subscription_benefit_family_desc">Mantén a todos en la misma página</string>
|
||||||
|
<string name="onboarding_subscription_benefit_insights">Análisis de gastos</string>
|
||||||
|
<string name="onboarding_subscription_benefit_insights_desc">Mira en qué se va tu dinero</string>
|
||||||
|
<string name="onboarding_subscription_benefit_contractor_sharing">Compartir contratistas</string>
|
||||||
|
<string name="onboarding_subscription_benefit_contractor_sharing_desc">Comparte tus contratistas de confianza con familia y amigos</string>
|
||||||
|
<string name="onboarding_subscription_benefit_widgets">Widgets de pantalla de inicio</string>
|
||||||
|
<string name="onboarding_subscription_benefit_widgets_desc">Acciones rápidas desde tu pantalla de inicio</string>
|
||||||
|
<string name="onboarding_subscription_benefit_notifications">Notificaciones con acciones</string>
|
||||||
|
<string name="onboarding_subscription_benefit_notifications_desc">Completa tareas directamente desde las notificaciones</string>
|
||||||
|
<string name="onboarding_subscription_choose_plan">Elige tu plan</string>
|
||||||
|
<string name="onboarding_subscription_monthly">Mensual</string>
|
||||||
|
<string name="onboarding_subscription_yearly">Anual</string>
|
||||||
|
<string name="onboarding_subscription_save">Ahorra 30%</string>
|
||||||
|
<string name="onboarding_subscription_monthly_price">$2.99/mes</string>
|
||||||
|
<string name="onboarding_subscription_yearly_price">$23.99/año</string>
|
||||||
|
<string name="onboarding_subscription_yearly_monthly">Solo $1.99/mes</string>
|
||||||
|
<string name="onboarding_subscription_start_trial">Comenzar prueba gratis de 7 días</string>
|
||||||
|
<string name="onboarding_subscription_continue_free">Continuar con la versión gratuita</string>
|
||||||
|
<string name="onboarding_subscription_trial_terms">Prueba gratis de 7 días, luego %1$s. Cancela cuando quieras.</string>
|
||||||
|
<string name="onboarding_home_profile_title">Cuéntanos sobre tu hogar</string>
|
||||||
|
<string name="onboarding_home_profile_subtitle">Todo opcional: nos ayuda a personalizar tu plan de mantenimiento</string>
|
||||||
|
<string name="onboarding_home_profile_systems">Sistemas</string>
|
||||||
|
<string name="onboarding_home_profile_features">Características</string>
|
||||||
|
<string name="onboarding_home_profile_exterior">Exterior</string>
|
||||||
|
<string name="onboarding_home_profile_interior">Interior</string>
|
||||||
|
<string name="for_you_tab">Para ti</string>
|
||||||
|
<string name="browse_tab">Explorar</string>
|
||||||
|
<string name="biometric_lock_title">App bloqueada</string>
|
||||||
|
<string name="biometric_lock_description">Autentícate para desbloquear honeyDue</string>
|
||||||
|
<string name="biometric_lock_setting_title">Bloqueo biométrico</string>
|
||||||
|
<string name="biometric_lock_setting_subtitle">Requerir autenticación al abrir la app</string>
|
||||||
|
<string name="biometric_prompt_title">Desbloquear honeyDue</string>
|
||||||
|
<string name="biometric_prompt_subtitle">Verifica tu identidad para continuar</string>
|
||||||
|
<string name="biometric_unlock_button">Desbloquear con biometría</string>
|
||||||
|
<string name="biometric_auth_failed">Error de autenticación</string>
|
||||||
|
<string name="biometric_not_available">La autenticación biométrica no está disponible en este dispositivo</string>
|
||||||
|
<string name="notif_channel_task_reminder_name">Recordatorios de tareas</string>
|
||||||
|
<string name="notif_channel_task_reminder_description">Recordatorios de tareas próximas y de vencimiento cercano</string>
|
||||||
|
<string name="notif_channel_task_overdue_name">Tareas vencidas</string>
|
||||||
|
<string name="notif_channel_task_overdue_description">Alertas cuando una tarea pasa su fecha de vencimiento</string>
|
||||||
|
<string name="notif_channel_residence_invite_name">Invitaciones a residencias</string>
|
||||||
|
<string name="notif_channel_residence_invite_description">Invitaciones para unirse a una residencia compartida</string>
|
||||||
|
<string name="notif_channel_subscription_name">Actualizaciones de suscripción</string>
|
||||||
|
<string name="notif_channel_subscription_description">Estado de la suscripción y actualizaciones de facturación</string>
|
||||||
|
<string name="common_selected">Seleccionado</string>
|
||||||
|
<string name="common_open">Abrir</string>
|
||||||
|
<string name="common_skip">Omitir</string>
|
||||||
|
<string name="common_or">o</string>
|
||||||
|
<string name="common_info">Información</string>
|
||||||
|
<string name="common_verified">Verificado</string>
|
||||||
|
<string name="common_warning">Advertencia</string>
|
||||||
|
<string name="common_photo">Foto</string>
|
||||||
|
<string name="common_included">Incluido</string>
|
||||||
|
<string name="common_verifying">Verificando…</string>
|
||||||
|
<string name="time_am">AM</string>
|
||||||
|
<string name="time_pm">PM</string>
|
||||||
|
<string name="time_eve">NOCHE</string>
|
||||||
|
<string name="error_network_title">Error de red</string>
|
||||||
|
<string name="image_failed_to_load">Error al cargar</string>
|
||||||
|
<string name="theme_appearance_title">Apariencia</string>
|
||||||
|
<string name="theme_use_system_colors">Usar colores del sistema</string>
|
||||||
|
<string name="theme_material_you_desc">Seguir Material You de Android 12+ (colores del fondo de pantalla)</string>
|
||||||
|
<string name="paywall_choose_plan_title">Elige tu plan</string>
|
||||||
|
<string name="paywall_choose_plan_subtitle">Mejora a Pro para acceso ilimitado</string>
|
||||||
|
<string name="paywall_upgrade_to_pro">Mejorar a Pro</string>
|
||||||
|
<string name="paywall_col_feature">Función</string>
|
||||||
|
<string name="paywall_col_free">Gratis</string>
|
||||||
|
<string name="paywall_col_pro">Pro</string>
|
||||||
|
<string name="paywall_feat_properties">Propiedades</string>
|
||||||
|
<string name="paywall_feat_tasks">Tareas</string>
|
||||||
|
<string name="paywall_feat_contractors">Contratistas</string>
|
||||||
|
<string name="paywall_feat_documents">Documentos</string>
|
||||||
|
<string name="paywall_val_1_property">1 propiedad</string>
|
||||||
|
<string name="paywall_val_10_tasks">10 tareas</string>
|
||||||
|
<string name="paywall_val_unlimited">Ilimitado</string>
|
||||||
|
<string name="paywall_val_not_available">No disponible</string>
|
||||||
|
<string name="reset_check_email_title">Revisa tu correo</string>
|
||||||
|
<string name="reset_sent_code_to">Enviamos un código de 6 dígitos a</string>
|
||||||
|
<string name="reset_code_expires">El código caduca en 15 minutos</string>
|
||||||
|
<string name="reset_enter_code_hint">Introduce el código de 6 dígitos de tu correo</string>
|
||||||
|
<string name="reset_code_verified_msg">¡Código verificado! Ahora establece tu nueva contraseña</string>
|
||||||
|
<string name="reset_didnt_receive">¿No recibiste el código?</string>
|
||||||
|
<string name="reset_check_spam">Revisa tu carpeta de spam si no lo ves</string>
|
||||||
|
<string name="reset_verify_failed_title">Verificación del código fallida</string>
|
||||||
|
<string name="verify_email_required_msg">Se requiere verificación de correo. Revisa tu bandeja de entrada para ver un código de 6 dígitos.</string>
|
||||||
|
<string name="verify_email_invalid_code">Introduce un código válido de 6 dígitos</string>
|
||||||
|
<string name="verify_email_didnt_receive">¿No recibiste el código? Revisa tu carpeta de spam o contacta con soporte.</string>
|
||||||
|
<string name="verify_email_failed_title">Verificación fallida</string>
|
||||||
|
<string name="reset_pw_success_msg">Tu contraseña se ha restablecido correctamente</string>
|
||||||
|
<string name="reset_pw_can_login">Ya puedes iniciar sesión con tu nueva contraseña</string>
|
||||||
|
<string name="reset_return_to_login">Volver al inicio de sesión</string>
|
||||||
|
<string name="reset_set_new_pw_title">Establecer nueva contraseña</string>
|
||||||
|
<string name="reset_create_strong_pw">Crea una contraseña segura para proteger tu cuenta</string>
|
||||||
|
<string name="reset_pw_requirements">Requisitos de la contraseña</string>
|
||||||
|
<string name="reset_pw_failed_title">Restablecimiento de contraseña fallido</string>
|
||||||
|
<string name="forgot_send_code_hint">Enviaremos un código de verificación de 6 dígitos a esta dirección</string>
|
||||||
|
<string name="forgot_check_email_msg">Revisa tu correo para ver un código de verificación de 6 dígitos</string>
|
||||||
|
<string name="forgot_back_to_login">¿Recuerdas tu contraseña? Volver al inicio de sesión</string>
|
||||||
|
<string name="forgot_send_failed_title">Error al enviar el código de restablecimiento</string>
|
||||||
|
<string name="join_property_title">Unirse a propiedad</string>
|
||||||
|
<string name="join_shared_property_header">Unirse a una propiedad compartida</string>
|
||||||
|
<string name="join_enter_code_desc">Introduce el código de 6 caracteres proporcionado por el propietario.</string>
|
||||||
|
<string name="join_share_code_label">Código para compartir</string>
|
||||||
|
<string name="join_share_code_placeholder">ABC123</string>
|
||||||
|
<string name="join_code_helper">Los códigos tienen 6 caracteres en mayúscula</string>
|
||||||
|
<string name="join_joining">Uniéndose…</string>
|
||||||
|
<string name="biometric_enter_pin">Introduce el PIN para desbloquear</string>
|
||||||
|
<string name="biometric_pin_label">PIN de 4 dígitos</string>
|
||||||
|
<string name="biometric_incorrect_pin">PIN incorrecto</string>
|
||||||
|
<string name="biometric_unlock">Desbloquear</string>
|
||||||
|
<string name="tasks_create_failed_title">Error al crear la tarea</string>
|
||||||
|
<string name="tasks_all_title">Todas las tareas</string>
|
||||||
|
<string name="tasks_add">Añadir tarea</string>
|
||||||
|
<string name="tasks_load_failed_title">Error al cargar las tareas</string>
|
||||||
|
<string name="tasks_all_empty_subtitle">Crea tu primera tarea para empezar</string>
|
||||||
|
<string name="tasks_add_property_first">Primero añade una propiedad desde la pestaña Residencias</string>
|
||||||
|
<string name="residences_upgrade_to_add">Mejorar para añadir</string>
|
||||||
|
<string name="residences_primary_cd">Residencia principal</string>
|
||||||
|
<string name="residence_unit_label">Unidad: %1$s</string>
|
||||||
|
<string name="residence_error_loading_tasks">Error al cargar las tareas: %1$s</string>
|
||||||
|
<string name="residence_error_loading_contractors">Error al cargar los contratistas: %1$s</string>
|
||||||
|
<string name="profile_subscription_cd">Suscripción</string>
|
||||||
|
<string name="onboarding_sub_cancel_note">Cancela cuando quieras en Ajustes • Sin compromiso</string>
|
||||||
|
<string name="onboarding_joining_residence">Uniéndose a la residencia…</string>
|
||||||
|
<string name="onboarding_sub_trial_legal">Prueba gratuita de 7 días, luego %1$s</string>
|
||||||
|
<string name="common_collapse">Contraer</string>
|
||||||
|
<string name="common_expand">Expandir</string>
|
||||||
|
<string name="common_not_selected">No seleccionado</string>
|
||||||
|
<string name="auth_logging_in">Iniciando sesión…</string>
|
||||||
|
<string name="auth_requirement_met">Requisito cumplido</string>
|
||||||
|
<string name="auth_requirement_not_met">Requisito no cumplido</string>
|
||||||
|
<string name="error_something_wrong">Algo salió mal</string>
|
||||||
|
<string name="paywall_feat_not_included">No incluido</string>
|
||||||
|
<string name="completions_contractor_prefix">Contratista:</string>
|
||||||
|
<string name="completions_completed_by_prefix">Completado por:</string>
|
||||||
|
<string name="completions_remove_photo">Quitar foto</string>
|
||||||
|
<string name="completions_star_rating_cd">%1$d estrellas</string>
|
||||||
|
<string name="photos_completion_title">Fotos de finalización</string>
|
||||||
|
<string name="photos_task_completion_cd">Foto de finalización de la tarea</string>
|
||||||
|
<string name="documents_residence_ref">Residencia n.º %1$d</string>
|
||||||
|
<string name="documents_task_ref">Tarea n.º %1$d</string>
|
||||||
|
<string name="documents_image_index">Imagen %1$d de %2$d</string>
|
||||||
|
<string name="documents_images_title">Imágenes del documento</string>
|
||||||
|
<string name="documents_empty_no_warranties">No se encontraron garantías</string>
|
||||||
|
<string name="documents_empty_no_documents">No se encontraron documentos</string>
|
||||||
|
<string name="documents_days_remaining_count">%1$d días restantes</string>
|
||||||
|
<string name="contractors_property_ref">Propiedad n.º</string>
|
||||||
|
<string name="residence_share_failed">Error al compartir la residencia</string>
|
||||||
|
<string name="theme_default">Predeterminado</string>
|
||||||
|
<string name="theme_default_desc">Colores vibrantes del sistema iOS</string>
|
||||||
|
<string name="theme_teal">Verde azulado</string>
|
||||||
|
<string name="theme_teal_desc">Azul verdoso con acentos cálidos</string>
|
||||||
|
<string name="theme_ocean">Océano</string>
|
||||||
|
<string name="theme_ocean_desc">Azules profundos y tonos coral</string>
|
||||||
|
<string name="theme_forest">Bosque</string>
|
||||||
|
<string name="theme_forest_desc">Verdes tierra y matices dorados</string>
|
||||||
|
<string name="theme_sunset">Atardecer</string>
|
||||||
|
<string name="theme_sunset_desc">Naranjas y rojos cálidos</string>
|
||||||
|
<string name="theme_monochrome">Monocromo</string>
|
||||||
|
<string name="theme_monochrome_desc">Elegante escala de grises</string>
|
||||||
|
<string name="theme_lavender">Lavanda</string>
|
||||||
|
<string name="theme_lavender_desc">Púrpura suave con acentos rosados</string>
|
||||||
|
<string name="theme_crimson">Carmesí</string>
|
||||||
|
<string name="theme_crimson_desc">Rojo intenso con destellos cálidos</string>
|
||||||
|
<string name="theme_midnight">Medianoche</string>
|
||||||
|
<string name="theme_midnight_desc">Azul marino profundo con azul cielo</string>
|
||||||
|
<string name="theme_desert">Desierto</string>
|
||||||
|
<string name="theme_desert_desc">Tonos cálidos de terracota y arena</string>
|
||||||
|
<string name="theme_mint">Menta</string>
|
||||||
|
<string name="theme_mint_desc">Verde fresco con turquesa</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -472,4 +472,585 @@
|
|||||||
<string name="subscription_features">Fonctionnalites Premium</string>
|
<string name="subscription_features">Fonctionnalites Premium</string>
|
||||||
<string name="subscription_limit_properties">Vous avez atteint la limite de proprietes de votre forfait</string>
|
<string name="subscription_limit_properties">Vous avez atteint la limite de proprietes de votre forfait</string>
|
||||||
<string name="subscription_limit_tasks">Vous avez atteint la limite de taches de votre forfait</string>
|
<string name="subscription_limit_tasks">Vous avez atteint la limite de taches de votre forfait</string>
|
||||||
|
<string name="home_profile_heating">Chauffage</string>
|
||||||
|
<string name="home_profile_cooling">Climatisation</string>
|
||||||
|
<string name="home_profile_water_heater">Chauffe-eau</string>
|
||||||
|
<string name="home_profile_pool">Piscine</string>
|
||||||
|
<string name="home_profile_sprinkler_system">Système d\'arrosage</string>
|
||||||
|
<string name="home_profile_fireplace">Cheminée</string>
|
||||||
|
<string name="home_profile_garage">Garage</string>
|
||||||
|
<string name="home_profile_basement">Sous-sol</string>
|
||||||
|
<string name="home_profile_attic">Grenier</string>
|
||||||
|
<string name="home_profile_septic">Fosse septique</string>
|
||||||
|
<string name="home_profile_roof_type">Type de toiture</string>
|
||||||
|
<string name="home_profile_exterior">Extérieur</string>
|
||||||
|
<string name="home_profile_flooring">Revêtement de sol</string>
|
||||||
|
<string name="home_profile_landscaping">Aménagement paysager</string>
|
||||||
|
<string name="onboarding_first_task_selected_count">%1$d tâches sélectionnées</string>
|
||||||
|
<string name="onboarding_first_task_add_continue">Ajouter %1$d tâches et continuer</string>
|
||||||
|
<string name="onboarding_first_task_finding">Recherche de tâches pour votre logement...</string>
|
||||||
|
<string name="onboarding_first_task_no_suggestions">Pas encore de suggestions personnalisées — parcourez le catalogue complet ou ignorez cette étape.</string>
|
||||||
|
<string name="onboarding_first_task_browse_all">Tout parcourir</string>
|
||||||
|
<string name="onboarding_first_task_skip">Ignorer</string>
|
||||||
|
<string name="onboarding_first_task_suggestions_error">Impossible de charger vos suggestions</string>
|
||||||
|
<string name="onboarding_first_task_connection_error">Vérifiez votre connexion et réessayez.</string>
|
||||||
|
<string name="onboarding_first_task_loading_catalog">Chargement du catalogue de tâches...</string>
|
||||||
|
<string name="onboarding_first_task_catalog_error">Impossible de charger le catalogue de tâches</string>
|
||||||
|
<string name="onboarding_first_task_no_templates">Aucun modèle disponible pour le moment.</string>
|
||||||
|
<string name="onboarding_first_task_selected">Sélectionné</string>
|
||||||
|
<string name="onboarding_first_task_offline">Hors ligne</string>
|
||||||
|
<string name="onboarding_first_task_skip_for_now">Ignorer pour l\'instant</string>
|
||||||
|
<string name="upgrade_hero_title">Passer à Pro</string>
|
||||||
|
<string name="upgrade_hero_subtitle">Exploitez tout le potentiel de honeyDue</string>
|
||||||
|
<string name="upgrade_choose_plan">Choisissez votre formule</string>
|
||||||
|
<string name="upgrade_plan_yearly">Annuel</string>
|
||||||
|
<string name="upgrade_plan_monthly">Mensuel</string>
|
||||||
|
<string name="upgrade_plan_save_50">Économisez 50 %</string>
|
||||||
|
<string name="upgrade_best_value">MEILLEURE OFFRE</string>
|
||||||
|
<string name="upgrade_whats_included">Ce qui est inclus</string>
|
||||||
|
<string name="upgrade_feature_unlimited_properties">Propriétés illimitées</string>
|
||||||
|
<string name="upgrade_feature_unlimited_properties_desc">Suivez l\'entretien de tous vos logements</string>
|
||||||
|
<string name="upgrade_feature_unlimited_tasks">Tâches illimitées</string>
|
||||||
|
<string name="upgrade_feature_unlimited_tasks_desc">N\'oubliez plus jamais une tâche d\'entretien</string>
|
||||||
|
<string name="upgrade_feature_contractor_management">Gestion des prestataires</string>
|
||||||
|
<string name="upgrade_feature_contractor_management_desc">Enregistrez et notez vos prestataires de confiance</string>
|
||||||
|
<string name="upgrade_feature_document_vault">Coffre-fort de documents</string>
|
||||||
|
<string name="upgrade_feature_document_vault_desc">Conservez garanties, reçus et manuels</string>
|
||||||
|
<string name="upgrade_feature_family_sharing">Partage familial</string>
|
||||||
|
<string name="upgrade_feature_family_sharing_desc">Invitez des membres de la famille à collaborer</string>
|
||||||
|
<string name="upgrade_feature_smart_reminders">Rappels intelligents</string>
|
||||||
|
<string name="upgrade_feature_smart_reminders_desc">Soyez averti à l\'échéance des tâches</string>
|
||||||
|
<string name="upgrade_feature_document_warranty_storage">Stockage de documents et garanties</string>
|
||||||
|
<string name="upgrade_subscribe_now">S\'abonner maintenant</string>
|
||||||
|
<string name="upgrade_restore_purchases">Restaurer les achats</string>
|
||||||
|
<string name="upgrade_terms_text">L\'abonnement se renouvelle automatiquement sauf annulation au moins 24 heures avant la fin de la période en cours. Gérez les abonnements dans les réglages de votre appareil.</string>
|
||||||
|
<string name="upgrade_terms_of_use">Conditions d\'utilisation</string>
|
||||||
|
<string name="upgrade_included">Inclus</string>
|
||||||
|
<string name="upgrade_compare_free_vs_pro">Comparer Gratuit et Pro</string>
|
||||||
|
<string name="upgrade_maybe_later">Plus tard</string>
|
||||||
|
<string name="upgrade_warning">Avertissement</string>
|
||||||
|
<string name="upgrade_subscription_active">Abonnement actif</string>
|
||||||
|
<string name="upgrade_subscription_active_message">Vous avez désormais un accès complet à toutes les fonctionnalités Pro !</string>
|
||||||
|
<string name="upgrade_feature_required_title">Mise à niveau requise</string>
|
||||||
|
<string name="upgrade_feature_required_message">Cette fonctionnalité est disponible avec un abonnement Pro.</string>
|
||||||
|
<string name="upgrade_prompt_default_message">Débloquez un accès illimité à toutes les fonctionnalités</string>
|
||||||
|
<string name="upgrade_product_monthly_name">honeyDue Pro Mensuel</string>
|
||||||
|
<string name="upgrade_product_annual_name">honeyDue Pro Annuel</string>
|
||||||
|
<string name="upgrade_billed_monthly">Facturé mensuellement</string>
|
||||||
|
<string name="upgrade_billed_annually">Facturé annuellement</string>
|
||||||
|
<string name="upgrade_save_22">Économisez 22 %</string>
|
||||||
|
<string name="tasks_column_done">Terminé</string>
|
||||||
|
<string name="tasks_column_archived">Archivé</string>
|
||||||
|
<string name="tasks_column_empty">Aucune tâche</string>
|
||||||
|
<string name="tasks_new_title">Nouvelle tâche</string>
|
||||||
|
<string name="tasks_title_field_label">Titre</string>
|
||||||
|
<string name="tasks_title_placeholder">ex. Vidanger le chauffe-eau</string>
|
||||||
|
<string name="tasks_description_placeholder">Détails facultatifs</string>
|
||||||
|
<string name="tasks_due_date_optional_label">Date d\'échéance (facultatif)</string>
|
||||||
|
<string name="tasks_due_date_placeholder_format">aaaa-MM-jj</string>
|
||||||
|
<string name="tasks_due_date_blank_helper">Laissez vide pour aucune date d\'échéance</string>
|
||||||
|
<string name="tasks_estimated_cost_optional_label">Coût estimé (facultatif)</string>
|
||||||
|
<string name="suggestions_title">Tâches suggérées</string>
|
||||||
|
<string name="suggestions_skip">Ignorer</string>
|
||||||
|
<string name="suggestions_accept">Accepter</string>
|
||||||
|
<string name="suggestions_load_failed">Impossible de charger les suggestions</string>
|
||||||
|
<string name="suggestions_empty_title">Pas encore de suggestions</string>
|
||||||
|
<string name="suggestions_empty_subtitle">Complétez le profil de votre logement pour voir des recommandations personnalisées.</string>
|
||||||
|
<string name="completion_history_title">Historique des achèvements</string>
|
||||||
|
<string name="completion_history_count_one">%1$d achèvement</string>
|
||||||
|
<string name="completion_history_count_other">%1$d achèvements</string>
|
||||||
|
<string name="completion_history_loading">Chargement des achèvements...</string>
|
||||||
|
<string name="completion_history_load_failed">Échec du chargement des achèvements</string>
|
||||||
|
<string name="completion_history_empty_title">Pas encore d\'achèvements</string>
|
||||||
|
<string name="completion_history_empty_message">Cette tâche n\'a pas été terminée.</string>
|
||||||
|
<string name="completion_history_completed_by">Terminé par %1$s</string>
|
||||||
|
<string name="completion_history_view_photo">Voir la photo</string>
|
||||||
|
<string name="manage_users_remove_user">Retirer l\'utilisateur</string>
|
||||||
|
<string name="manage_users_copy_code">Copier le code</string>
|
||||||
|
<string name="manage_users_code_copied">Code copié dans le presse-papiers</string>
|
||||||
|
<string name="manage_users_load_failed">Impossible de charger les utilisateurs</string>
|
||||||
|
<string name="manage_users_remove_confirm">Retirer %1$s de cette propriété ?</string>
|
||||||
|
<string name="properties_shared_users_count">Utilisateurs partagés (%1$d)</string>
|
||||||
|
<string name="properties_no_shared_users">Aucun utilisateur partagé</string>
|
||||||
|
<string name="properties_shared_users_helper">Utilisateurs ayant accès à cette résidence. Utilisez le bouton de partage pour inviter d\'autres personnes.</string>
|
||||||
|
<string name="properties_remove_user_confirm">Êtes-vous sûr de vouloir retirer %1$s de cette résidence ?</string>
|
||||||
|
<string name="properties_remove_button">Retirer</string>
|
||||||
|
<string name="documents_expires_label">Expire le</string>
|
||||||
|
<string name="auth_password_requirement_length">Au moins 8 caractères</string>
|
||||||
|
<string name="auth_password_requirement_uppercase">Contient une majuscule</string>
|
||||||
|
<string name="auth_password_requirement_lowercase">Contient une minuscule</string>
|
||||||
|
<string name="auth_password_requirement_digit">Contient un chiffre</string>
|
||||||
|
<string name="auth_password_requirement_match">Les mots de passe correspondent</string>
|
||||||
|
<string name="auth_password_requirements_title">Exigences du mot de passe</string>
|
||||||
|
<string name="auth_password_complexity_error">Le mot de passe doit comporter au moins 8 caractères, avec au moins une majuscule, une minuscule et un chiffre</string>
|
||||||
|
<string name="properties_join_residence_title">Rejoindre la résidence</string>
|
||||||
|
<string name="properties_join_residence_message">Voulez-vous rejoindre cette résidence partagée ?</string>
|
||||||
|
<string name="properties_join_success">Résidence rejointe</string>
|
||||||
|
<string name="properties_join_success_message">Vous avez maintenant accès à %1$s.</string>
|
||||||
|
<string name="properties_join_failed">Échec de l\'ajout</string>
|
||||||
|
<string name="properties_joining">Connexion…</string>
|
||||||
|
<string name="properties_shared_by">Partagé par : %1$s</string>
|
||||||
|
<string name="properties_expires">Expire le : %1$s</string>
|
||||||
|
<string name="properties_share_upgrade_title">Fonction Pro</string>
|
||||||
|
<string name="properties_share_upgrade_message">Le partage de résidences est une fonction Pro. Passez à Pro pour inviter votre famille à collaborer à l\'entretien de la maison.</string>
|
||||||
|
<string name="tasks_failed_to_cancel">Échec de l\'annulation de la tâche</string>
|
||||||
|
<string name="tasks_failed_to_restore">Échec de la restauration de la tâche</string>
|
||||||
|
<string name="tasks_failed_to_mark_in_progress">Impossible de marquer la tâche en cours</string>
|
||||||
|
<string name="tasks_failed_to_archive">Échec de l\'archivage de la tâche</string>
|
||||||
|
<string name="tasks_failed_to_unarchive">Échec du désarchivage de la tâche</string>
|
||||||
|
<string name="tasks_card_in_progress">EN COURS</string>
|
||||||
|
<string name="tasks_card_actions">Actions</string>
|
||||||
|
<string name="tasks_card_mark_in_progress">Marquer en cours</string>
|
||||||
|
<string name="tasks_card_complete_task">Terminer la tâche</string>
|
||||||
|
<string name="tasks_card_edit_task">Modifier la tâche</string>
|
||||||
|
<string name="tasks_card_cancel_task">Annuler la tâche</string>
|
||||||
|
<string name="tasks_card_restore_task">Restaurer la tâche</string>
|
||||||
|
<string name="tasks_card_archive_task">Archiver la tâche</string>
|
||||||
|
<string name="tasks_card_unarchive_task">Désarchiver la tâche</string>
|
||||||
|
<string name="tasks_card_not_available">N/D</string>
|
||||||
|
<string name="tasks_card_completed_by">Par : %1$s</string>
|
||||||
|
<string name="tasks_card_cost">Coût : %1$s $</string>
|
||||||
|
<string name="tasks_card_view_photos">Voir les photos (%1$d)</string>
|
||||||
|
<string name="tasks_add_new">Nouvelle tâche</string>
|
||||||
|
<string name="tasks_property_required">Propriété *</string>
|
||||||
|
<string name="tasks_property_error">La propriété est requise</string>
|
||||||
|
<string name="tasks_browse_templates">Parcourir les modèles de tâches</string>
|
||||||
|
<string name="tasks_common_tasks">%1$d tâches courantes</string>
|
||||||
|
<string name="tasks_category_error">La catégorie est requise</string>
|
||||||
|
<string name="tasks_interval_days">Intervalle (jours)</string>
|
||||||
|
<string name="tasks_interval_override">Remplacer l\'intervalle par défaut</string>
|
||||||
|
<string name="tasks_custom_interval_help">Nombre de jours entre chaque occurrence</string>
|
||||||
|
<string name="tasks_due_date_format_error">L\'échéance est requise (format : AAAA-MM-JJ)</string>
|
||||||
|
<string name="tasks_due_date_format">Format : AAAA-MM-JJ</string>
|
||||||
|
<string name="tasks_create">Créer la tâche</string>
|
||||||
|
<string name="tasks_in_progress_label">En cours</string>
|
||||||
|
<string name="templates_title">Modèles de tâches</string>
|
||||||
|
<string name="templates_done">Terminé</string>
|
||||||
|
<string name="templates_search_placeholder">Rechercher des modèles…</string>
|
||||||
|
<string name="templates_clear">Effacer</string>
|
||||||
|
<string name="templates_result">résultat</string>
|
||||||
|
<string name="templates_results">résultats</string>
|
||||||
|
<string name="templates_no_results_title">Aucun modèle trouvé</string>
|
||||||
|
<string name="templates_no_results_message">Essayez un autre terme de recherche</string>
|
||||||
|
<string name="templates_empty_title">Aucun modèle disponible</string>
|
||||||
|
<string name="templates_empty_message">Les modèles apparaîtront ici une fois chargés</string>
|
||||||
|
<string name="templates_expand">Développer</string>
|
||||||
|
<string name="templates_collapse">Réduire</string>
|
||||||
|
<string name="templates_add">Ajouter</string>
|
||||||
|
<string name="templates_all_categories">Tous</string>
|
||||||
|
<string name="templates_apply">Appliquer</string>
|
||||||
|
<string name="templates_apply_count">Appliquer (%1$d)</string>
|
||||||
|
<string name="templates_selected_count">%1$d sélectionnés</string>
|
||||||
|
<string name="templates_retry">Réessayer</string>
|
||||||
|
<string name="templates_load_failed">Échec du chargement des modèles</string>
|
||||||
|
<string name="templates_create_failed">Échec de la création des tâches</string>
|
||||||
|
<string name="completions_complete_task_title">Terminer la tâche : %1$s</string>
|
||||||
|
<string name="completions_select_contractor">Sélectionner un prestataire (facultatif)</string>
|
||||||
|
<string name="completions_choose_contractor_placeholder">Choisissez un prestataire ou laissez vide</string>
|
||||||
|
<string name="completions_expand">Développer</string>
|
||||||
|
<string name="completions_none_manual">Aucun (saisie manuelle)</string>
|
||||||
|
<string name="completions_loading_contractors">Chargement des prestataires…</string>
|
||||||
|
<string name="completions_error_loading_contractors">Erreur de chargement des prestataires</string>
|
||||||
|
<string name="completions_completed_by_name">Réalisé par (facultatif)</string>
|
||||||
|
<string name="completions_completed_by_placeholder">Saisissez un nom si vous n\'utilisez pas le prestataire ci-dessus</string>
|
||||||
|
<string name="completions_actual_cost_optional">Coût réel (facultatif)</string>
|
||||||
|
<string name="completions_notes_optional">Notes (facultatif)</string>
|
||||||
|
<string name="completions_rating">Note : %1$d sur 5</string>
|
||||||
|
<string name="completions_add_images">Ajouter des images</string>
|
||||||
|
<string name="completions_take_photo">Prendre une photo</string>
|
||||||
|
<string name="completions_choose_from_library">Choisir dans la bibliothèque</string>
|
||||||
|
<string name="completions_images_selected">%1$d image(s) sélectionnée(s)</string>
|
||||||
|
<string name="completions_remove_image">Supprimer l\'image</string>
|
||||||
|
<string name="completions_complete_button">Terminer</string>
|
||||||
|
<string name="completions_quality_rating">Note de qualité</string>
|
||||||
|
<string name="completions_photos_count">Photos (%1$d/%2$d)</string>
|
||||||
|
<string name="completions_camera">Appareil photo</string>
|
||||||
|
<string name="completions_library">Bibliothèque</string>
|
||||||
|
<string name="completions_add_photos_helper">Ajoutez des photos du travail terminé (facultatif)</string>
|
||||||
|
<string name="completions_contractor_helper">Associer cette réalisation à un prestataire</string>
|
||||||
|
<string name="completions_details_section">Détails de la réalisation</string>
|
||||||
|
<string name="completions_optional_info">Tous les champs sont facultatifs</string>
|
||||||
|
<string name="completions_notes_helper">Ajoutez des notes sur le travail terminé</string>
|
||||||
|
<string name="completions_notes_placeholder">Décrivez le travail effectué, les problèmes rencontrés, etc.</string>
|
||||||
|
<string name="completions_rate_quality">Évaluez la qualité du travail effectué</string>
|
||||||
|
<string name="completions_enter_manually">Saisissez un nom manuellement ci-dessous</string>
|
||||||
|
<string name="manage_users_title">Gérer les utilisateurs</string>
|
||||||
|
<string name="manage_users_invite_title">Inviter d\'autres personnes</string>
|
||||||
|
<string name="manage_users_easy_share">Partage facile</string>
|
||||||
|
<string name="manage_users_send_invite">Envoyer un lien d\'invitation</string>
|
||||||
|
<string name="manage_users_easy_share_desc">Envoyez un fichier .honeydue par messages, e-mail ou partage. Il suffit d\'appuyer pour rejoindre.</string>
|
||||||
|
<string name="manage_users_share_code">Code de partage</string>
|
||||||
|
<string name="manage_users_no_code">Aucun code actif</string>
|
||||||
|
<string name="manage_users_generate">Générer un code</string>
|
||||||
|
<string name="manage_users_generate_new">Générer un nouveau code</string>
|
||||||
|
<string name="manage_users_code_desc">Partagez ce code à 6 caractères. Ils peuvent le saisir dans l\'app pour rejoindre.</string>
|
||||||
|
<string name="manage_users_users_count">Utilisateurs (%1$d)</string>
|
||||||
|
<string name="manage_users_owner_badge">Propriétaire</string>
|
||||||
|
<string name="manage_users_remove">Retirer</string>
|
||||||
|
<string name="manage_users_or">ou</string>
|
||||||
|
<string name="contractors_share">Partager le prestataire</string>
|
||||||
|
<string name="contractors_share_upgrade_title">Fonction Pro</string>
|
||||||
|
<string name="contractors_share_upgrade_message">Le partage de prestataires est une fonction Pro. Passez à Pro pour partager vos prestataires de confiance avec vos proches.</string>
|
||||||
|
<string name="contractors_import_title">Importer le prestataire</string>
|
||||||
|
<string name="contractors_import_message">Voulez-vous importer ce prestataire ?</string>
|
||||||
|
<string name="contractors_import_success">Prestataire importé</string>
|
||||||
|
<string name="contractors_import_success_message">%1$s a été ajouté à vos contacts.</string>
|
||||||
|
<string name="contractors_import_failed">Échec de l\'importation</string>
|
||||||
|
<string name="contractors_shared_by">Partagé par : %1$s</string>
|
||||||
|
<string name="contractors_form_add_title">Ajouter un prestataire</string>
|
||||||
|
<string name="contractors_form_edit_title">Modifier le prestataire</string>
|
||||||
|
<string name="contractors_form_basic_info">Informations de base</string>
|
||||||
|
<string name="contractors_form_name_required">Nom *</string>
|
||||||
|
<string name="contractors_form_company">Entreprise</string>
|
||||||
|
<string name="contractors_form_residence_optional">Résidence (facultatif)</string>
|
||||||
|
<string name="contractors_form_personal_no_residence">Personnel (aucune résidence)</string>
|
||||||
|
<string name="contractors_form_personal_visibility">Vous seul verrez ce prestataire</string>
|
||||||
|
<string name="contractors_form_shared_visibility">Tous les utilisateurs de %1$s verront ce prestataire</string>
|
||||||
|
<string name="contractors_form_contact_info">Coordonnées</string>
|
||||||
|
<string name="contractors_form_phone">Téléphone</string>
|
||||||
|
<string name="contractors_form_email">E-mail</string>
|
||||||
|
<string name="contractors_form_website">Site web</string>
|
||||||
|
<string name="contractors_form_specialties">Spécialités</string>
|
||||||
|
<string name="contractors_form_address_section">Adresse</string>
|
||||||
|
<string name="contractors_form_street_address">Adresse</string>
|
||||||
|
<string name="contractors_form_city">Ville</string>
|
||||||
|
<string name="contractors_form_state">État</string>
|
||||||
|
<string name="contractors_form_zip_code">Code postal</string>
|
||||||
|
<string name="contractors_form_notes_section">Notes</string>
|
||||||
|
<string name="contractors_form_private_notes">Notes privées</string>
|
||||||
|
<string name="contractors_form_mark_favorite">Marquer comme favori</string>
|
||||||
|
<string name="contractors_form_add_button">Ajouter</string>
|
||||||
|
<string name="contractors_form_save_button">Enregistrer</string>
|
||||||
|
<string name="documents_form_edit_warranty">Modifier la garantie</string>
|
||||||
|
<string name="documents_form_edit_document">Modifier le document</string>
|
||||||
|
<string name="documents_form_add_warranty">Ajouter une garantie</string>
|
||||||
|
<string name="documents_form_add_document">Ajouter un document</string>
|
||||||
|
<string name="documents_form_select_residence">Sélectionner une résidence</string>
|
||||||
|
<string name="documents_form_residence_required">Résidence *</string>
|
||||||
|
<string name="documents_form_document_type_required">Type de document *</string>
|
||||||
|
<string name="documents_form_title_required">Titre *</string>
|
||||||
|
<string name="documents_form_item_name_required">Nom de l\'article *</string>
|
||||||
|
<string name="documents_form_model_number">Numéro de modèle</string>
|
||||||
|
<string name="documents_form_serial_number">Numéro de série</string>
|
||||||
|
<string name="documents_form_provider_required">Fournisseur/Entreprise *</string>
|
||||||
|
<string name="documents_form_provider_contact">Contact du fournisseur</string>
|
||||||
|
<string name="documents_form_claim_phone">Téléphone de réclamation</string>
|
||||||
|
<string name="documents_form_claim_email">E-mail de réclamation</string>
|
||||||
|
<string name="documents_form_claim_website">Site web de réclamation</string>
|
||||||
|
<string name="documents_form_purchase_date">Date d\'achat (AAAA-MM-JJ)</string>
|
||||||
|
<string name="documents_form_warranty_start">Début de la garantie (AAAA-MM-JJ)</string>
|
||||||
|
<string name="documents_form_warranty_end_required">Fin de la garantie (AAAA-MM-JJ) *</string>
|
||||||
|
<string name="documents_form_description">Description</string>
|
||||||
|
<string name="documents_form_category">Catégorie</string>
|
||||||
|
<string name="documents_form_select_category">Sélectionner une catégorie</string>
|
||||||
|
<string name="documents_form_category_none">Aucune</string>
|
||||||
|
<string name="documents_form_tags">Étiquettes</string>
|
||||||
|
<string name="documents_form_tags_placeholder">étiquette1, étiquette2, étiquette3</string>
|
||||||
|
<string name="documents_form_notes">Notes</string>
|
||||||
|
<string name="documents_form_active">Active</string>
|
||||||
|
<string name="documents_form_existing_photos">Photos existantes (%1$d)</string>
|
||||||
|
<string name="documents_form_new_photos">Nouvelles photos (%1$d/%2$d)</string>
|
||||||
|
<string name="documents_form_photos">Photos (%1$d/%2$d)</string>
|
||||||
|
<string name="documents_form_camera">Appareil photo</string>
|
||||||
|
<string name="documents_form_gallery">Galerie</string>
|
||||||
|
<string name="documents_form_image_number">Image %1$d</string>
|
||||||
|
<string name="documents_form_remove_image">Supprimer l\'image</string>
|
||||||
|
<string name="documents_form_update_warranty">Mettre à jour la garantie</string>
|
||||||
|
<string name="documents_form_update_document">Mettre à jour le document</string>
|
||||||
|
<string name="documents_form_select_residence_error">Veuillez sélectionner une résidence</string>
|
||||||
|
<string name="documents_form_title_error">Le titre est requis</string>
|
||||||
|
<string name="documents_form_item_name_error">Le nom de l\'article est requis pour les garanties</string>
|
||||||
|
<string name="documents_form_provider_error">Le fournisseur est requis pour les garanties</string>
|
||||||
|
<string name="documents_form_date_placeholder">2024-01-15</string>
|
||||||
|
<string name="documents_form_date_placeholder_end">2025-01-15</string>
|
||||||
|
<string name="documents_form_failed_to_load_residences">Échec du chargement des résidences : %1$s</string>
|
||||||
|
<string name="profile_support">Assistance</string>
|
||||||
|
<string name="profile_contact_support">Contacter l\'assistance</string>
|
||||||
|
<string name="profile_contact_support_subtitle">Obtenez de l\'aide pour votre compte</string>
|
||||||
|
<string name="profile_upgrade_benefits_title">Débloquez les fonctions Premium</string>
|
||||||
|
<string name="profile_upgrade_benefits_subtitle">Passez à Pro pour l\'expérience complète</string>
|
||||||
|
<string name="profile_benefit_unlimited_properties">Propriétés illimitées</string>
|
||||||
|
<string name="profile_benefit_document_vault">Stockage des documents et garanties</string>
|
||||||
|
<string name="profile_benefit_residence_sharing">Partage de résidence</string>
|
||||||
|
<string name="profile_benefit_contractor_sharing">Partage de prestataires</string>
|
||||||
|
<string name="profile_benefit_actionable_notifications">Notifications interactives</string>
|
||||||
|
<string name="profile_benefit_widgets">Widgets d\'écran d\'accueil</string>
|
||||||
|
<string name="profile_privacy">Politique de confidentialité</string>
|
||||||
|
<string name="profile_privacy_subtitle">Consulter notre politique de confidentialité</string>
|
||||||
|
<string name="profile_app_version">Version %1$s</string>
|
||||||
|
<string name="profile_app_name">honeyDue</string>
|
||||||
|
<string name="profile_edit_profile">Modifier le profil</string>
|
||||||
|
<string name="delete_account_title">Supprimer le compte</string>
|
||||||
|
<string name="delete_account_subtitle">Supprimer définitivement votre compte</string>
|
||||||
|
<string name="delete_account_warning">Cette action est définitive et irréversible. Toutes vos données seront supprimées.</string>
|
||||||
|
<string name="delete_account_shared_warning">Toute résidence que vous possédez et partagez avec d\'autres utilisateurs sera également supprimée.</string>
|
||||||
|
<string name="delete_account_confirm_password">Saisissez votre mot de passe pour confirmer</string>
|
||||||
|
<string name="delete_account_confirm_type">Saisissez SUPPRIMER pour confirmer</string>
|
||||||
|
<string name="delete_account_button">Supprimer mon compte</string>
|
||||||
|
<string name="delete_account_cancel">Annuler</string>
|
||||||
|
<string name="delete_account_success">Compte supprimé avec succès</string>
|
||||||
|
<string name="delete_account_failed">Échec de la suppression du compte</string>
|
||||||
|
<string name="notifications_daily_digest">Récapitulatif quotidien</string>
|
||||||
|
<string name="notifications_daily_digest_desc">Aperçu quotidien des tâches à échéance et en retard</string>
|
||||||
|
<string name="notifications_email_section">Notifications par e-mail</string>
|
||||||
|
<string name="notifications_email_task_completed">E-mail de tâche terminée</string>
|
||||||
|
<string name="notifications_email_task_completed_desc">Recevez un e-mail lorsqu\'une tâche est terminée</string>
|
||||||
|
<string name="notifications_set_custom_time">Définir une heure personnalisée</string>
|
||||||
|
<string name="notifications_change_time">Modifier</string>
|
||||||
|
<string name="notifications_select_time">Sélectionner l\'heure de notification</string>
|
||||||
|
<string name="notifications_master_title">Toutes les notifications</string>
|
||||||
|
<string name="notifications_master_desc">Activez ou désactivez toutes les catégories en un seul geste</string>
|
||||||
|
<string name="notifications_categories_section">Catégories</string>
|
||||||
|
<string name="notifications_category_task_reminder">Rappels de tâches</string>
|
||||||
|
<string name="notifications_category_task_reminder_desc">Rappels à venir et bientôt dus</string>
|
||||||
|
<string name="notifications_category_task_overdue">Tâches en retard</string>
|
||||||
|
<string name="notifications_category_task_overdue_desc">Alertes lorsqu\'une tâche dépasse son échéance</string>
|
||||||
|
<string name="notifications_category_residence_invite">Invitations de résidence</string>
|
||||||
|
<string name="notifications_category_residence_invite_desc">Invitations à rejoindre une résidence partagée</string>
|
||||||
|
<string name="notifications_category_subscription">Mises à jour d\'abonnement</string>
|
||||||
|
<string name="notifications_category_subscription_desc">Changements de facturation et de statut du forfait</string>
|
||||||
|
<string name="notifications_open_system_settings">Ouvrir les paramètres système</string>
|
||||||
|
<string name="notifications_system_settings_desc">Ajustez les sons, les badges et le mode Ne pas déranger dans les paramètres Android</string>
|
||||||
|
<string name="common_share">Partager</string>
|
||||||
|
<string name="common_import">Importer</string>
|
||||||
|
<string name="common_importing">Importation…</string>
|
||||||
|
<string name="common_try_again">Réessayer</string>
|
||||||
|
<string name="home_overdue">En retard</string>
|
||||||
|
<string name="home_due_this_week">À faire cette semaine</string>
|
||||||
|
<string name="home_next_30_days">30 prochains jours</string>
|
||||||
|
<string name="home_your_properties">Vos propriétés</string>
|
||||||
|
<string name="onboarding_welcome_title">Bienvenue dans honeyDue</string>
|
||||||
|
<string name="onboarding_welcome_subtitle">Votre compagnon d\'entretien de la maison</string>
|
||||||
|
<string name="onboarding_start_fresh">Commencer à zéro</string>
|
||||||
|
<string name="onboarding_join_existing">Rejoindre une maison existante</string>
|
||||||
|
<string name="onboarding_already_have_account">Vous avez déjà un compte ? Connectez-vous</string>
|
||||||
|
<string name="onboarding_skip">Passer</string>
|
||||||
|
<string name="onboarding_continue">Continuer</string>
|
||||||
|
<string name="onboarding_get_started">Commencer</string>
|
||||||
|
<string name="onboarding_feature_tasks_title">N\'oubliez jamais une tâche</string>
|
||||||
|
<string name="onboarding_feature_tasks_desc">Suivez toutes vos tâches d\'entretien au même endroit avec des rappels intelligents</string>
|
||||||
|
<string name="onboarding_feature_docs_title">Vos documents à portée de main</string>
|
||||||
|
<string name="onboarding_feature_docs_desc">Conservez garanties, manuels et reçus en toute sécurité et accédez-y à tout moment</string>
|
||||||
|
<string name="onboarding_feature_contractors_title">Vos prestataires de confiance</string>
|
||||||
|
<string name="onboarding_feature_contractors_desc">Gardez tous vos contacts de prestataires organisés et facilement accessibles</string>
|
||||||
|
<string name="onboarding_feature_family_title">Partagez avec votre famille</string>
|
||||||
|
<string name="onboarding_feature_family_desc">Invitez votre famille à collaborer ensemble à l\'entretien de la maison</string>
|
||||||
|
<string name="onboarding_feature_notifications_title">Notifications intelligentes</string>
|
||||||
|
<string name="onboarding_feature_notifications_desc">Recevez des rappels interactifs qui vous permettent de terminer les tâches directement depuis la notification</string>
|
||||||
|
<string name="onboarding_feature_widgets_title">Widgets d\'écran d\'accueil</string>
|
||||||
|
<string name="onboarding_feature_widgets_desc">Accès rapide aux tâches et rappels directement depuis votre écran d\'accueil</string>
|
||||||
|
<string name="onboarding_location_title">Où se trouve votre maison ?</string>
|
||||||
|
<string name="onboarding_location_subtitle">Nous suggérerons des tâches d\'entretien adaptées au climat de votre région</string>
|
||||||
|
<string name="onboarding_location_use_my_location">Utiliser ma position</string>
|
||||||
|
<string name="onboarding_location_detecting">Détection…</string>
|
||||||
|
<string name="onboarding_location_enter_zip">Saisir un code postal à la place</string>
|
||||||
|
<string name="onboarding_location_enter_zip_prompt">Saisissez votre code postal</string>
|
||||||
|
<string name="onboarding_location_zip_placeholder">Code postal</string>
|
||||||
|
<string name="onboarding_name_residence_title">Nommez votre maison</string>
|
||||||
|
<string name="onboarding_name_residence_subtitle">Donnez un nom à votre propriété pour l\'identifier facilement</string>
|
||||||
|
<string name="onboarding_name_residence_placeholder">ex. : Ma maison, Maison de plage, Appartement</string>
|
||||||
|
<string name="onboarding_name_residence_hint">Vous pourrez ajouter plus de détails plus tard</string>
|
||||||
|
<string name="onboarding_create_account_title">Sauvegardez votre maison</string>
|
||||||
|
<string name="onboarding_create_account_subtitle">Créez un compte pour synchroniser sur tous vos appareils</string>
|
||||||
|
<string name="onboarding_create_with_email">Créer un compte avec un e-mail</string>
|
||||||
|
<string name="onboarding_verify_email_title">Vérifiez votre e-mail</string>
|
||||||
|
<string name="onboarding_verify_email_subtitle">Nous avons envoyé un code à 6 chiffres à votre adresse e-mail. Saisissez-le ci-dessous pour vérifier votre compte.</string>
|
||||||
|
<string name="onboarding_verify_email_hint">Vous n\'avez pas reçu de code ? Vérifiez vos courriers indésirables</string>
|
||||||
|
<string name="onboarding_join_title">Rejoindre une résidence</string>
|
||||||
|
<string name="onboarding_join_subtitle">Saisissez le code à 6 caractères qui vous a été partagé pour rejoindre une maison existante</string>
|
||||||
|
<string name="onboarding_join_placeholder">Saisir le code de partage</string>
|
||||||
|
<string name="onboarding_join_button">Rejoindre la résidence</string>
|
||||||
|
<string name="onboarding_tasks_title">Tout est prêt !</string>
|
||||||
|
<string name="onboarding_tasks_subtitle">Commençons par quelques tâches. Plus vous en choisissez, plus nous vous aiderons à vous en souvenir !</string>
|
||||||
|
<string name="onboarding_tasks_selected">%1$d/%2$d tâches sélectionnées</string>
|
||||||
|
<string name="onboarding_tasks_add_popular">Ajouter les plus populaires</string>
|
||||||
|
<string name="onboarding_tasks_continue">Ajouter %1$d tâches et continuer</string>
|
||||||
|
<string name="onboarding_tasks_skip">Passer pour l\'instant</string>
|
||||||
|
<string name="onboarding_category_hvac">CVC et climat</string>
|
||||||
|
<string name="onboarding_category_safety">Sécurité et sûreté</string>
|
||||||
|
<string name="onboarding_category_plumbing">Plomberie</string>
|
||||||
|
<string name="onboarding_category_outdoor">Extérieur et pelouse</string>
|
||||||
|
<string name="onboarding_category_appliances">Électroménager</string>
|
||||||
|
<string name="onboarding_category_general">Maison générale</string>
|
||||||
|
<string name="onboarding_subscription_title">Passez à Pro</string>
|
||||||
|
<string name="onboarding_subscription_subtitle">Faites passer la gestion de votre maison au niveau supérieur</string>
|
||||||
|
<string name="onboarding_subscription_pro">HONEYDUE PRO</string>
|
||||||
|
<string name="onboarding_subscription_social_proof">4,9 • plus de 10 000 propriétaires</string>
|
||||||
|
<string name="onboarding_subscription_benefit_properties">Propriétés illimitées</string>
|
||||||
|
<string name="onboarding_subscription_benefit_properties_desc">Suivez chaque maison que vous possédez</string>
|
||||||
|
<string name="onboarding_subscription_benefit_reminders">Rappels intelligents</string>
|
||||||
|
<string name="onboarding_subscription_benefit_reminders_desc">Ne manquez jamais une échéance d\'entretien</string>
|
||||||
|
<string name="onboarding_subscription_benefit_documents">Coffre à documents</string>
|
||||||
|
<string name="onboarding_subscription_benefit_documents_desc">Tous vos documents au même endroit</string>
|
||||||
|
<string name="onboarding_subscription_benefit_family">Partage familial</string>
|
||||||
|
<string name="onboarding_subscription_benefit_family_desc">Mettez tout le monde sur la même longueur d\'onde</string>
|
||||||
|
<string name="onboarding_subscription_benefit_insights">Analyse des dépenses</string>
|
||||||
|
<string name="onboarding_subscription_benefit_insights_desc">Voyez où va votre argent</string>
|
||||||
|
<string name="onboarding_subscription_benefit_contractor_sharing">Partage de prestataires</string>
|
||||||
|
<string name="onboarding_subscription_benefit_contractor_sharing_desc">Partagez vos prestataires de confiance avec vos proches et amis</string>
|
||||||
|
<string name="onboarding_subscription_benefit_widgets">Widgets d\'écran d\'accueil</string>
|
||||||
|
<string name="onboarding_subscription_benefit_widgets_desc">Actions rapides directement depuis votre écran d\'accueil</string>
|
||||||
|
<string name="onboarding_subscription_benefit_notifications">Notifications interactives</string>
|
||||||
|
<string name="onboarding_subscription_benefit_notifications_desc">Terminez les tâches directement depuis les notifications</string>
|
||||||
|
<string name="onboarding_subscription_choose_plan">Choisissez votre forfait</string>
|
||||||
|
<string name="onboarding_subscription_monthly">Mensuel</string>
|
||||||
|
<string name="onboarding_subscription_yearly">Annuel</string>
|
||||||
|
<string name="onboarding_subscription_save">Économisez 30 %</string>
|
||||||
|
<string name="onboarding_subscription_monthly_price">2,99 $/mois</string>
|
||||||
|
<string name="onboarding_subscription_yearly_price">23,99 $/an</string>
|
||||||
|
<string name="onboarding_subscription_yearly_monthly">Seulement 1,99 $/mois</string>
|
||||||
|
<string name="onboarding_subscription_start_trial">Commencer l\'essai gratuit de 7 jours</string>
|
||||||
|
<string name="onboarding_subscription_continue_free">Continuer avec la version gratuite</string>
|
||||||
|
<string name="onboarding_subscription_trial_terms">Essai gratuit de 7 jours, puis %1$s. Annulable à tout moment.</string>
|
||||||
|
<string name="onboarding_home_profile_title">Parlez-nous de votre maison</string>
|
||||||
|
<string name="onboarding_home_profile_subtitle">Tout est facultatif — cela nous aide à personnaliser votre plan d\'entretien</string>
|
||||||
|
<string name="onboarding_home_profile_systems">Systèmes</string>
|
||||||
|
<string name="onboarding_home_profile_features">Caractéristiques</string>
|
||||||
|
<string name="onboarding_home_profile_exterior">Extérieur</string>
|
||||||
|
<string name="onboarding_home_profile_interior">Intérieur</string>
|
||||||
|
<string name="for_you_tab">Pour vous</string>
|
||||||
|
<string name="browse_tab">Parcourir</string>
|
||||||
|
<string name="biometric_lock_title">Application verrouillée</string>
|
||||||
|
<string name="biometric_lock_description">Authentifiez-vous pour déverrouiller honeyDue</string>
|
||||||
|
<string name="biometric_lock_setting_title">Verrouillage biométrique</string>
|
||||||
|
<string name="biometric_lock_setting_subtitle">Exiger une authentification à l\'ouverture de l\'app</string>
|
||||||
|
<string name="biometric_prompt_title">Déverrouiller honeyDue</string>
|
||||||
|
<string name="biometric_prompt_subtitle">Vérifiez votre identité pour continuer</string>
|
||||||
|
<string name="biometric_unlock_button">Déverrouiller avec la biométrie</string>
|
||||||
|
<string name="biometric_auth_failed">Échec de l\'authentification</string>
|
||||||
|
<string name="biometric_not_available">L\'authentification biométrique n\'est pas disponible sur cet appareil</string>
|
||||||
|
<string name="notif_channel_task_reminder_name">Rappels de tâches</string>
|
||||||
|
<string name="notif_channel_task_reminder_description">Rappels de tâches à venir et bientôt dues</string>
|
||||||
|
<string name="notif_channel_task_overdue_name">Tâches en retard</string>
|
||||||
|
<string name="notif_channel_task_overdue_description">Alertes lorsqu\'une tâche dépasse son échéance</string>
|
||||||
|
<string name="notif_channel_residence_invite_name">Invitations de résidence</string>
|
||||||
|
<string name="notif_channel_residence_invite_description">Invitations à rejoindre une résidence partagée</string>
|
||||||
|
<string name="notif_channel_subscription_name">Mises à jour d\'abonnement</string>
|
||||||
|
<string name="notif_channel_subscription_description">Mises à jour du statut d\'abonnement et de la facturation</string>
|
||||||
|
<string name="common_selected">Sélectionné</string>
|
||||||
|
<string name="common_open">Ouvrir</string>
|
||||||
|
<string name="common_skip">Ignorer</string>
|
||||||
|
<string name="common_or">ou</string>
|
||||||
|
<string name="common_info">Infos</string>
|
||||||
|
<string name="common_verified">Vérifié</string>
|
||||||
|
<string name="common_warning">Avertissement</string>
|
||||||
|
<string name="common_photo">Photo</string>
|
||||||
|
<string name="common_included">Inclus</string>
|
||||||
|
<string name="common_verifying">Vérification…</string>
|
||||||
|
<string name="time_am">AM</string>
|
||||||
|
<string name="time_pm">PM</string>
|
||||||
|
<string name="time_eve">SOIR</string>
|
||||||
|
<string name="error_network_title">Erreur réseau</string>
|
||||||
|
<string name="image_failed_to_load">Échec du chargement</string>
|
||||||
|
<string name="theme_appearance_title">Apparence</string>
|
||||||
|
<string name="theme_use_system_colors">Utiliser les couleurs du système</string>
|
||||||
|
<string name="theme_material_you_desc">Suivre Material You d\'Android 12+ (couleurs du fond d\'écran)</string>
|
||||||
|
<string name="paywall_choose_plan_title">Choisissez votre forfait</string>
|
||||||
|
<string name="paywall_choose_plan_subtitle">Passez à Pro pour un accès illimité</string>
|
||||||
|
<string name="paywall_upgrade_to_pro">Passer à Pro</string>
|
||||||
|
<string name="paywall_col_feature">Fonctionnalité</string>
|
||||||
|
<string name="paywall_col_free">Gratuit</string>
|
||||||
|
<string name="paywall_col_pro">Pro</string>
|
||||||
|
<string name="paywall_feat_properties">Propriétés</string>
|
||||||
|
<string name="paywall_feat_tasks">Tâches</string>
|
||||||
|
<string name="paywall_feat_contractors">Prestataires</string>
|
||||||
|
<string name="paywall_feat_documents">Documents</string>
|
||||||
|
<string name="paywall_val_1_property">1 propriété</string>
|
||||||
|
<string name="paywall_val_10_tasks">10 tâches</string>
|
||||||
|
<string name="paywall_val_unlimited">Illimité</string>
|
||||||
|
<string name="paywall_val_not_available">Non disponible</string>
|
||||||
|
<string name="reset_check_email_title">Vérifiez vos e-mails</string>
|
||||||
|
<string name="reset_sent_code_to">Nous avons envoyé un code à 6 chiffres à</string>
|
||||||
|
<string name="reset_code_expires">Le code expire dans 15 minutes</string>
|
||||||
|
<string name="reset_enter_code_hint">Saisissez le code à 6 chiffres reçu par e-mail</string>
|
||||||
|
<string name="reset_code_verified_msg">Code vérifié ! Définissez maintenant votre nouveau mot de passe</string>
|
||||||
|
<string name="reset_didnt_receive">Vous n\'avez pas reçu le code ?</string>
|
||||||
|
<string name="reset_check_spam">Vérifiez votre dossier spam si vous ne le voyez pas</string>
|
||||||
|
<string name="reset_verify_failed_title">Échec de la vérification du code</string>
|
||||||
|
<string name="verify_email_required_msg">La vérification de l\'e-mail est requise. Vérifiez votre boîte de réception pour un code à 6 chiffres.</string>
|
||||||
|
<string name="verify_email_invalid_code">Veuillez saisir un code valide à 6 chiffres</string>
|
||||||
|
<string name="verify_email_didnt_receive">Vous n\'avez pas reçu le code ? Vérifiez votre dossier spam ou contactez le support.</string>
|
||||||
|
<string name="verify_email_failed_title">Échec de la vérification</string>
|
||||||
|
<string name="reset_pw_success_msg">Votre mot de passe a été réinitialisé avec succès</string>
|
||||||
|
<string name="reset_pw_can_login">Vous pouvez maintenant vous connecter avec votre nouveau mot de passe</string>
|
||||||
|
<string name="reset_return_to_login">Retour à la connexion</string>
|
||||||
|
<string name="reset_set_new_pw_title">Définir un nouveau mot de passe</string>
|
||||||
|
<string name="reset_create_strong_pw">Créez un mot de passe fort pour sécuriser votre compte</string>
|
||||||
|
<string name="reset_pw_requirements">Exigences du mot de passe</string>
|
||||||
|
<string name="reset_pw_failed_title">Échec de la réinitialisation du mot de passe</string>
|
||||||
|
<string name="forgot_send_code_hint">Nous enverrons un code de vérification à 6 chiffres à cette adresse</string>
|
||||||
|
<string name="forgot_check_email_msg">Vérifiez vos e-mails pour un code de vérification à 6 chiffres</string>
|
||||||
|
<string name="forgot_back_to_login">Vous vous souvenez de votre mot de passe ? Retour à la connexion</string>
|
||||||
|
<string name="forgot_send_failed_title">Échec de l\'envoi du code de réinitialisation</string>
|
||||||
|
<string name="join_property_title">Rejoindre une propriété</string>
|
||||||
|
<string name="join_shared_property_header">Rejoindre une propriété partagée</string>
|
||||||
|
<string name="join_enter_code_desc">Saisissez le code à 6 caractères fourni par le propriétaire.</string>
|
||||||
|
<string name="join_share_code_label">Code de partage</string>
|
||||||
|
<string name="join_share_code_placeholder">ABC123</string>
|
||||||
|
<string name="join_code_helper">Les codes comportent 6 caractères en majuscules</string>
|
||||||
|
<string name="join_joining">Connexion…</string>
|
||||||
|
<string name="biometric_enter_pin">Saisissez le code PIN pour déverrouiller</string>
|
||||||
|
<string name="biometric_pin_label">Code PIN à 4 chiffres</string>
|
||||||
|
<string name="biometric_incorrect_pin">Code PIN incorrect</string>
|
||||||
|
<string name="biometric_unlock">Déverrouiller</string>
|
||||||
|
<string name="tasks_create_failed_title">Échec de la création de la tâche</string>
|
||||||
|
<string name="tasks_all_title">Toutes les tâches</string>
|
||||||
|
<string name="tasks_add">Ajouter une tâche</string>
|
||||||
|
<string name="tasks_load_failed_title">Échec du chargement des tâches</string>
|
||||||
|
<string name="tasks_all_empty_subtitle">Créez votre première tâche pour commencer</string>
|
||||||
|
<string name="tasks_add_property_first">Ajoutez d\'abord une propriété depuis l\'onglet Résidences</string>
|
||||||
|
<string name="residences_upgrade_to_add">Passez à la version supérieure pour ajouter</string>
|
||||||
|
<string name="residences_primary_cd">Résidence principale</string>
|
||||||
|
<string name="residence_unit_label">Unité : %1$s</string>
|
||||||
|
<string name="residence_error_loading_tasks">Erreur lors du chargement des tâches : %1$s</string>
|
||||||
|
<string name="residence_error_loading_contractors">Erreur lors du chargement des prestataires : %1$s</string>
|
||||||
|
<string name="profile_subscription_cd">Abonnement</string>
|
||||||
|
<string name="onboarding_sub_cancel_note">Annulez à tout moment dans les Paramètres • Sans engagement</string>
|
||||||
|
<string name="onboarding_joining_residence">Connexion à la résidence…</string>
|
||||||
|
<string name="onboarding_sub_trial_legal">Essai gratuit de 7 jours, puis %1$s</string>
|
||||||
|
<string name="common_collapse">Réduire</string>
|
||||||
|
<string name="common_expand">Développer</string>
|
||||||
|
<string name="common_not_selected">Non sélectionné</string>
|
||||||
|
<string name="auth_logging_in">Connexion…</string>
|
||||||
|
<string name="auth_requirement_met">Exigence satisfaite</string>
|
||||||
|
<string name="auth_requirement_not_met">Exigence non satisfaite</string>
|
||||||
|
<string name="error_something_wrong">Une erreur s\'est produite</string>
|
||||||
|
<string name="paywall_feat_not_included">Non inclus</string>
|
||||||
|
<string name="completions_contractor_prefix">Prestataire :</string>
|
||||||
|
<string name="completions_completed_by_prefix">Terminé par :</string>
|
||||||
|
<string name="completions_remove_photo">Supprimer la photo</string>
|
||||||
|
<string name="completions_star_rating_cd">%1$d étoiles</string>
|
||||||
|
<string name="photos_completion_title">Photos de finalisation</string>
|
||||||
|
<string name="photos_task_completion_cd">Photo de finalisation de la tâche</string>
|
||||||
|
<string name="documents_residence_ref">Résidence n° %1$d</string>
|
||||||
|
<string name="documents_task_ref">Tâche n° %1$d</string>
|
||||||
|
<string name="documents_image_index">Image %1$d sur %2$d</string>
|
||||||
|
<string name="documents_images_title">Images du document</string>
|
||||||
|
<string name="documents_empty_no_warranties">Aucune garantie trouvée</string>
|
||||||
|
<string name="documents_empty_no_documents">Aucun document trouvé</string>
|
||||||
|
<string name="documents_days_remaining_count">%1$d jours restants</string>
|
||||||
|
<string name="contractors_property_ref">Propriété n°</string>
|
||||||
|
<string name="residence_share_failed">Échec du partage de la résidence</string>
|
||||||
|
<string name="theme_default">Par défaut</string>
|
||||||
|
<string name="theme_default_desc">Couleurs vives du système iOS</string>
|
||||||
|
<string name="theme_teal">Sarcelle</string>
|
||||||
|
<string name="theme_teal_desc">Bleu-vert avec des accents chauds</string>
|
||||||
|
<string name="theme_ocean">Océan</string>
|
||||||
|
<string name="theme_ocean_desc">Bleus profonds et tons corail</string>
|
||||||
|
<string name="theme_forest">Forêt</string>
|
||||||
|
<string name="theme_forest_desc">Verts terre et nuances dorées</string>
|
||||||
|
<string name="theme_sunset">Coucher de soleil</string>
|
||||||
|
<string name="theme_sunset_desc">Oranges et rouges chauds</string>
|
||||||
|
<string name="theme_monochrome">Monochrome</string>
|
||||||
|
<string name="theme_monochrome_desc">Nuances de gris élégantes</string>
|
||||||
|
<string name="theme_lavender">Lavande</string>
|
||||||
|
<string name="theme_lavender_desc">Violet doux avec des accents rosés</string>
|
||||||
|
<string name="theme_crimson">Cramoisi</string>
|
||||||
|
<string name="theme_crimson_desc">Rouge intense avec des reflets chauds</string>
|
||||||
|
<string name="theme_midnight">Minuit</string>
|
||||||
|
<string name="theme_midnight_desc">Bleu marine profond avec bleu ciel</string>
|
||||||
|
<string name="theme_desert">Désert</string>
|
||||||
|
<string name="theme_desert_desc">Tons chauds de terre cuite et de sable</string>
|
||||||
|
<string name="theme_mint">Menthe</string>
|
||||||
|
<string name="theme_mint_desc">Vert frais avec turquoise</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -472,4 +472,585 @@
|
|||||||
<string name="subscription_features">Funzionalità Premium</string>
|
<string name="subscription_features">Funzionalità Premium</string>
|
||||||
<string name="subscription_limit_properties">Hai raggiunto il limite di proprietà per il tuo piano</string>
|
<string name="subscription_limit_properties">Hai raggiunto il limite di proprietà per il tuo piano</string>
|
||||||
<string name="subscription_limit_tasks">Hai raggiunto il limite di attività per il tuo piano</string>
|
<string name="subscription_limit_tasks">Hai raggiunto il limite di attività per il tuo piano</string>
|
||||||
|
<string name="home_profile_heating">Riscaldamento</string>
|
||||||
|
<string name="home_profile_cooling">Raffrescamento</string>
|
||||||
|
<string name="home_profile_water_heater">Scaldabagno</string>
|
||||||
|
<string name="home_profile_pool">Piscina</string>
|
||||||
|
<string name="home_profile_sprinkler_system">Impianto di irrigazione</string>
|
||||||
|
<string name="home_profile_fireplace">Camino</string>
|
||||||
|
<string name="home_profile_garage">Garage</string>
|
||||||
|
<string name="home_profile_basement">Seminterrato</string>
|
||||||
|
<string name="home_profile_attic">Soffitta</string>
|
||||||
|
<string name="home_profile_septic">Fossa settica</string>
|
||||||
|
<string name="home_profile_roof_type">Tipo di tetto</string>
|
||||||
|
<string name="home_profile_exterior">Esterno</string>
|
||||||
|
<string name="home_profile_flooring">Pavimentazione</string>
|
||||||
|
<string name="home_profile_landscaping">Giardinaggio</string>
|
||||||
|
<string name="onboarding_first_task_selected_count">%1$d attività selezionate</string>
|
||||||
|
<string name="onboarding_first_task_add_continue">Aggiungi %1$d attività e continua</string>
|
||||||
|
<string name="onboarding_first_task_finding">Ricerca di attività per la tua casa...</string>
|
||||||
|
<string name="onboarding_first_task_no_suggestions">Ancora nessun suggerimento personalizzato — sfoglia il catalogo completo o salta questo passaggio.</string>
|
||||||
|
<string name="onboarding_first_task_browse_all">Sfoglia tutto</string>
|
||||||
|
<string name="onboarding_first_task_skip">Salta</string>
|
||||||
|
<string name="onboarding_first_task_suggestions_error">Impossibile caricare i suggerimenti</string>
|
||||||
|
<string name="onboarding_first_task_connection_error">Controlla la connessione e riprova.</string>
|
||||||
|
<string name="onboarding_first_task_loading_catalog">Caricamento del catalogo attività...</string>
|
||||||
|
<string name="onboarding_first_task_catalog_error">Impossibile caricare il catalogo attività</string>
|
||||||
|
<string name="onboarding_first_task_no_templates">Nessun modello disponibile al momento.</string>
|
||||||
|
<string name="onboarding_first_task_selected">Selezionata</string>
|
||||||
|
<string name="onboarding_first_task_offline">Offline</string>
|
||||||
|
<string name="onboarding_first_task_skip_for_now">Salta per ora</string>
|
||||||
|
<string name="upgrade_hero_title">Passa a Pro</string>
|
||||||
|
<string name="upgrade_hero_subtitle">Sblocca tutto il potenziale di honeyDue</string>
|
||||||
|
<string name="upgrade_choose_plan">Scegli il tuo piano</string>
|
||||||
|
<string name="upgrade_plan_yearly">Annuale</string>
|
||||||
|
<string name="upgrade_plan_monthly">Mensile</string>
|
||||||
|
<string name="upgrade_plan_save_50">Risparmia 50%</string>
|
||||||
|
<string name="upgrade_best_value">MIGLIOR VALORE</string>
|
||||||
|
<string name="upgrade_whats_included">Cosa è incluso</string>
|
||||||
|
<string name="upgrade_feature_unlimited_properties">Proprietà illimitate</string>
|
||||||
|
<string name="upgrade_feature_unlimited_properties_desc">Gestisci la manutenzione di tutte le tue case</string>
|
||||||
|
<string name="upgrade_feature_unlimited_tasks">Attività illimitate</string>
|
||||||
|
<string name="upgrade_feature_unlimited_tasks_desc">Non dimenticare mai più un\'attività di manutenzione</string>
|
||||||
|
<string name="upgrade_feature_contractor_management">Gestione dei tecnici</string>
|
||||||
|
<string name="upgrade_feature_contractor_management_desc">Salva e valuta i tuoi tecnici di fiducia</string>
|
||||||
|
<string name="upgrade_feature_document_vault">Archivio documenti</string>
|
||||||
|
<string name="upgrade_feature_document_vault_desc">Conserva garanzie, ricevute e manuali</string>
|
||||||
|
<string name="upgrade_feature_family_sharing">Condivisione in famiglia</string>
|
||||||
|
<string name="upgrade_feature_family_sharing_desc">Invita i familiari a collaborare</string>
|
||||||
|
<string name="upgrade_feature_smart_reminders">Promemoria intelligenti</string>
|
||||||
|
<string name="upgrade_feature_smart_reminders_desc">Ricevi una notifica quando le attività sono in scadenza</string>
|
||||||
|
<string name="upgrade_feature_document_warranty_storage">Archiviazione di documenti e garanzie</string>
|
||||||
|
<string name="upgrade_subscribe_now">Abbonati ora</string>
|
||||||
|
<string name="upgrade_restore_purchases">Ripristina acquisti</string>
|
||||||
|
<string name="upgrade_terms_text">L\'abbonamento si rinnova automaticamente se non viene annullato almeno 24 ore prima della fine del periodo in corso. Gestisci gli abbonamenti nelle impostazioni del dispositivo.</string>
|
||||||
|
<string name="upgrade_terms_of_use">Condizioni d\'uso</string>
|
||||||
|
<string name="upgrade_included">Incluso</string>
|
||||||
|
<string name="upgrade_compare_free_vs_pro">Confronta Free e Pro</string>
|
||||||
|
<string name="upgrade_maybe_later">Forse più tardi</string>
|
||||||
|
<string name="upgrade_warning">Avviso</string>
|
||||||
|
<string name="upgrade_subscription_active">Abbonamento attivo</string>
|
||||||
|
<string name="upgrade_subscription_active_message">Ora hai accesso completo a tutte le funzioni Pro!</string>
|
||||||
|
<string name="upgrade_feature_required_title">Upgrade necessario</string>
|
||||||
|
<string name="upgrade_feature_required_message">Questa funzione è disponibile con un abbonamento Pro.</string>
|
||||||
|
<string name="upgrade_prompt_default_message">Sblocca l\'accesso illimitato a tutte le funzioni</string>
|
||||||
|
<string name="upgrade_product_monthly_name">honeyDue Pro Mensile</string>
|
||||||
|
<string name="upgrade_product_annual_name">honeyDue Pro Annuale</string>
|
||||||
|
<string name="upgrade_billed_monthly">Addebito mensile</string>
|
||||||
|
<string name="upgrade_billed_annually">Addebito annuale</string>
|
||||||
|
<string name="upgrade_save_22">Risparmia 22%</string>
|
||||||
|
<string name="tasks_column_done">Fatte</string>
|
||||||
|
<string name="tasks_column_archived">Archiviate</string>
|
||||||
|
<string name="tasks_column_empty">Nessuna attività</string>
|
||||||
|
<string name="tasks_new_title">Nuova attività</string>
|
||||||
|
<string name="tasks_title_field_label">Titolo</string>
|
||||||
|
<string name="tasks_title_placeholder">es. Spurga lo scaldabagno</string>
|
||||||
|
<string name="tasks_description_placeholder">Dettagli facoltativi</string>
|
||||||
|
<string name="tasks_due_date_optional_label">Data di scadenza (facoltativa)</string>
|
||||||
|
<string name="tasks_due_date_placeholder_format">aaaa-MM-gg</string>
|
||||||
|
<string name="tasks_due_date_blank_helper">Lascia vuoto per nessuna scadenza</string>
|
||||||
|
<string name="tasks_estimated_cost_optional_label">Costo stimato (facoltativo)</string>
|
||||||
|
<string name="suggestions_title">Attività suggerite</string>
|
||||||
|
<string name="suggestions_skip">Salta</string>
|
||||||
|
<string name="suggestions_accept">Accetta</string>
|
||||||
|
<string name="suggestions_load_failed">Impossibile caricare i suggerimenti</string>
|
||||||
|
<string name="suggestions_empty_title">Ancora nessun suggerimento</string>
|
||||||
|
<string name="suggestions_empty_subtitle">Completa il profilo della tua casa per vedere consigli personalizzati.</string>
|
||||||
|
<string name="completion_history_title">Cronologia completamenti</string>
|
||||||
|
<string name="completion_history_count_one">%1$d completamento</string>
|
||||||
|
<string name="completion_history_count_other">%1$d completamenti</string>
|
||||||
|
<string name="completion_history_loading">Caricamento dei completamenti...</string>
|
||||||
|
<string name="completion_history_load_failed">Impossibile caricare i completamenti</string>
|
||||||
|
<string name="completion_history_empty_title">Ancora nessun completamento</string>
|
||||||
|
<string name="completion_history_empty_message">Questa attività non è stata completata.</string>
|
||||||
|
<string name="completion_history_completed_by">Completata da %1$s</string>
|
||||||
|
<string name="completion_history_view_photo">Visualizza foto</string>
|
||||||
|
<string name="manage_users_remove_user">Rimuovi utente</string>
|
||||||
|
<string name="manage_users_copy_code">Copia codice</string>
|
||||||
|
<string name="manage_users_code_copied">Codice copiato negli appunti</string>
|
||||||
|
<string name="manage_users_load_failed">Impossibile caricare gli utenti</string>
|
||||||
|
<string name="manage_users_remove_confirm">Rimuovere %1$s da questa proprietà?</string>
|
||||||
|
<string name="properties_shared_users_count">Utenti condivisi (%1$d)</string>
|
||||||
|
<string name="properties_no_shared_users">Nessun utente condiviso</string>
|
||||||
|
<string name="properties_shared_users_helper">Utenti con accesso a questa residenza. Usa il pulsante di condivisione per invitare altri.</string>
|
||||||
|
<string name="properties_remove_user_confirm">Vuoi davvero rimuovere %1$s da questa residenza?</string>
|
||||||
|
<string name="properties_remove_button">Rimuovi</string>
|
||||||
|
<string name="documents_expires_label">Scade</string>
|
||||||
|
<string name="auth_password_requirement_length">Almeno 8 caratteri</string>
|
||||||
|
<string name="auth_password_requirement_uppercase">Contiene una lettera maiuscola</string>
|
||||||
|
<string name="auth_password_requirement_lowercase">Contiene una lettera minuscola</string>
|
||||||
|
<string name="auth_password_requirement_digit">Contiene un numero</string>
|
||||||
|
<string name="auth_password_requirement_match">Le password corrispondono</string>
|
||||||
|
<string name="auth_password_requirements_title">Requisiti password</string>
|
||||||
|
<string name="auth_password_complexity_error">La password deve contenere almeno 8 caratteri, con almeno una lettera maiuscola, una minuscola e un numero</string>
|
||||||
|
<string name="properties_join_residence_title">Unisciti alla residenza</string>
|
||||||
|
<string name="properties_join_residence_message">Vuoi unirti a questa residenza condivisa?</string>
|
||||||
|
<string name="properties_join_success">Ti sei unito alla residenza</string>
|
||||||
|
<string name="properties_join_success_message">Ora hai accesso a %1$s.</string>
|
||||||
|
<string name="properties_join_failed">Adesione non riuscita</string>
|
||||||
|
<string name="properties_joining">Adesione in corso...</string>
|
||||||
|
<string name="properties_shared_by">Condiviso da: %1$s</string>
|
||||||
|
<string name="properties_expires">Scade: %1$s</string>
|
||||||
|
<string name="properties_share_upgrade_title">Funzione Pro</string>
|
||||||
|
<string name="properties_share_upgrade_message">La condivisione delle residenze è una funzione Pro. Passa a Pro per invitare i familiari a collaborare alla manutenzione della casa.</string>
|
||||||
|
<string name="tasks_failed_to_cancel">Annullamento dell\'attività non riuscito</string>
|
||||||
|
<string name="tasks_failed_to_restore">Ripristino dell\'attività non riuscito</string>
|
||||||
|
<string name="tasks_failed_to_mark_in_progress">Impossibile contrassegnare l\'attività come in corso</string>
|
||||||
|
<string name="tasks_failed_to_archive">Archiviazione dell\'attività non riuscita</string>
|
||||||
|
<string name="tasks_failed_to_unarchive">Disarchiviazione dell\'attività non riuscita</string>
|
||||||
|
<string name="tasks_card_in_progress">IN CORSO</string>
|
||||||
|
<string name="tasks_card_actions">Azioni</string>
|
||||||
|
<string name="tasks_card_mark_in_progress">Segna come in corso</string>
|
||||||
|
<string name="tasks_card_complete_task">Completa attività</string>
|
||||||
|
<string name="tasks_card_edit_task">Modifica attività</string>
|
||||||
|
<string name="tasks_card_cancel_task">Annulla attività</string>
|
||||||
|
<string name="tasks_card_restore_task">Ripristina attività</string>
|
||||||
|
<string name="tasks_card_archive_task">Archivia attività</string>
|
||||||
|
<string name="tasks_card_unarchive_task">Disarchivia attività</string>
|
||||||
|
<string name="tasks_card_not_available">N/D</string>
|
||||||
|
<string name="tasks_card_completed_by">Da: %1$s</string>
|
||||||
|
<string name="tasks_card_cost">Costo: %1$s $</string>
|
||||||
|
<string name="tasks_card_view_photos">Vedi foto (%1$d)</string>
|
||||||
|
<string name="tasks_add_new">Aggiungi attività</string>
|
||||||
|
<string name="tasks_property_required">Proprietà *</string>
|
||||||
|
<string name="tasks_property_error">La proprietà è obbligatoria</string>
|
||||||
|
<string name="tasks_browse_templates">Sfoglia modelli di attività</string>
|
||||||
|
<string name="tasks_common_tasks">%1$d attività comuni</string>
|
||||||
|
<string name="tasks_category_error">La categoria è obbligatoria</string>
|
||||||
|
<string name="tasks_interval_days">Giorni di intervallo</string>
|
||||||
|
<string name="tasks_interval_override">Sostituisci l\'intervallo di frequenza predefinito</string>
|
||||||
|
<string name="tasks_custom_interval_help">Numero di giorni tra una ricorrenza e l\'altra</string>
|
||||||
|
<string name="tasks_due_date_format_error">La data di scadenza è obbligatoria (formato: AAAA-MM-GG)</string>
|
||||||
|
<string name="tasks_due_date_format">Formato: AAAA-MM-GG</string>
|
||||||
|
<string name="tasks_create">Crea attività</string>
|
||||||
|
<string name="tasks_in_progress_label">In corso</string>
|
||||||
|
<string name="templates_title">Modelli di attività</string>
|
||||||
|
<string name="templates_done">Fatto</string>
|
||||||
|
<string name="templates_search_placeholder">Cerca modelli...</string>
|
||||||
|
<string name="templates_clear">Cancella</string>
|
||||||
|
<string name="templates_result">risultato</string>
|
||||||
|
<string name="templates_results">risultati</string>
|
||||||
|
<string name="templates_no_results_title">Nessun modello trovato</string>
|
||||||
|
<string name="templates_no_results_message">Prova un altro termine di ricerca</string>
|
||||||
|
<string name="templates_empty_title">Nessun modello disponibile</string>
|
||||||
|
<string name="templates_empty_message">I modelli appariranno qui una volta caricati</string>
|
||||||
|
<string name="templates_expand">Espandi</string>
|
||||||
|
<string name="templates_collapse">Comprimi</string>
|
||||||
|
<string name="templates_add">Aggiungi</string>
|
||||||
|
<string name="templates_all_categories">Tutte</string>
|
||||||
|
<string name="templates_apply">Applica</string>
|
||||||
|
<string name="templates_apply_count">Applica (%1$d)</string>
|
||||||
|
<string name="templates_selected_count">%1$d selezionati</string>
|
||||||
|
<string name="templates_retry">Riprova</string>
|
||||||
|
<string name="templates_load_failed">Caricamento dei modelli non riuscito</string>
|
||||||
|
<string name="templates_create_failed">Creazione delle attività non riuscita</string>
|
||||||
|
<string name="completions_complete_task_title">Completa attività: %1$s</string>
|
||||||
|
<string name="completions_select_contractor">Seleziona fornitore (facoltativo)</string>
|
||||||
|
<string name="completions_choose_contractor_placeholder">Scegli un fornitore o lascia vuoto</string>
|
||||||
|
<string name="completions_expand">Espandi</string>
|
||||||
|
<string name="completions_none_manual">Nessuno (inserimento manuale)</string>
|
||||||
|
<string name="completions_loading_contractors">Caricamento fornitori...</string>
|
||||||
|
<string name="completions_error_loading_contractors">Errore nel caricamento dei fornitori</string>
|
||||||
|
<string name="completions_completed_by_name">Completato da (nome) (facoltativo)</string>
|
||||||
|
<string name="completions_completed_by_placeholder">Inserisci il nome se non usi il fornitore sopra</string>
|
||||||
|
<string name="completions_actual_cost_optional">Costo effettivo (facoltativo)</string>
|
||||||
|
<string name="completions_notes_optional">Note (facoltativo)</string>
|
||||||
|
<string name="completions_rating">Valutazione: %1$d su 5</string>
|
||||||
|
<string name="completions_add_images">Aggiungi immagini</string>
|
||||||
|
<string name="completions_take_photo">Scatta foto</string>
|
||||||
|
<string name="completions_choose_from_library">Scegli dalla libreria</string>
|
||||||
|
<string name="completions_images_selected">%1$d immagine/i selezionata/e</string>
|
||||||
|
<string name="completions_remove_image">Rimuovi immagine</string>
|
||||||
|
<string name="completions_complete_button">Completa</string>
|
||||||
|
<string name="completions_quality_rating">Valutazione qualità</string>
|
||||||
|
<string name="completions_photos_count">Foto (%1$d/%2$d)</string>
|
||||||
|
<string name="completions_camera">Fotocamera</string>
|
||||||
|
<string name="completions_library">Libreria</string>
|
||||||
|
<string name="completions_add_photos_helper">Aggiungi foto del lavoro completato (facoltativo)</string>
|
||||||
|
<string name="completions_contractor_helper">Collega il completamento di questa attività a un fornitore</string>
|
||||||
|
<string name="completions_details_section">Dettagli completamento</string>
|
||||||
|
<string name="completions_optional_info">Tutti i campi sono facoltativi</string>
|
||||||
|
<string name="completions_notes_helper">Aggiungi note sul lavoro completato</string>
|
||||||
|
<string name="completions_notes_placeholder">Descrivi il lavoro svolto, i problemi riscontrati, ecc.</string>
|
||||||
|
<string name="completions_rate_quality">Valuta la qualità del lavoro svolto</string>
|
||||||
|
<string name="completions_enter_manually">Inserisci il nome manualmente qui sotto</string>
|
||||||
|
<string name="manage_users_title">Gestisci utenti</string>
|
||||||
|
<string name="manage_users_invite_title">Invita altri</string>
|
||||||
|
<string name="manage_users_easy_share">Condivisione facile</string>
|
||||||
|
<string name="manage_users_send_invite">Invia link di invito</string>
|
||||||
|
<string name="manage_users_easy_share_desc">Invia un file .honeydue tramite Messaggi, email o condivisione. Basta toccare per unirsi.</string>
|
||||||
|
<string name="manage_users_share_code">Codice di condivisione</string>
|
||||||
|
<string name="manage_users_no_code">Nessun codice attivo</string>
|
||||||
|
<string name="manage_users_generate">Genera codice</string>
|
||||||
|
<string name="manage_users_generate_new">Genera nuovo codice</string>
|
||||||
|
<string name="manage_users_code_desc">Condividi questo codice di 6 caratteri. Possono inserirlo nell\'app per unirsi.</string>
|
||||||
|
<string name="manage_users_users_count">Utenti (%1$d)</string>
|
||||||
|
<string name="manage_users_owner_badge">Proprietario</string>
|
||||||
|
<string name="manage_users_remove">Rimuovi</string>
|
||||||
|
<string name="manage_users_or">oppure</string>
|
||||||
|
<string name="contractors_share">Condividi fornitore</string>
|
||||||
|
<string name="contractors_share_upgrade_title">Funzione Pro</string>
|
||||||
|
<string name="contractors_share_upgrade_message">La condivisione dei fornitori è una funzione Pro. Passa a Pro per condividere i tuoi fornitori di fiducia con amici e familiari.</string>
|
||||||
|
<string name="contractors_import_title">Importa fornitore</string>
|
||||||
|
<string name="contractors_import_message">Vuoi importare questo fornitore?</string>
|
||||||
|
<string name="contractors_import_success">Fornitore importato</string>
|
||||||
|
<string name="contractors_import_success_message">%1$s è stato aggiunto ai tuoi contatti.</string>
|
||||||
|
<string name="contractors_import_failed">Importazione non riuscita</string>
|
||||||
|
<string name="contractors_shared_by">Condiviso da: %1$s</string>
|
||||||
|
<string name="contractors_form_add_title">Aggiungi fornitore</string>
|
||||||
|
<string name="contractors_form_edit_title">Modifica fornitore</string>
|
||||||
|
<string name="contractors_form_basic_info">Informazioni di base</string>
|
||||||
|
<string name="contractors_form_name_required">Nome *</string>
|
||||||
|
<string name="contractors_form_company">Azienda</string>
|
||||||
|
<string name="contractors_form_residence_optional">Residenza (facoltativo)</string>
|
||||||
|
<string name="contractors_form_personal_no_residence">Personale (nessuna residenza)</string>
|
||||||
|
<string name="contractors_form_personal_visibility">Solo tu vedrai questo fornitore</string>
|
||||||
|
<string name="contractors_form_shared_visibility">Tutti gli utenti di %1$s vedranno questo fornitore</string>
|
||||||
|
<string name="contractors_form_contact_info">Informazioni di contatto</string>
|
||||||
|
<string name="contractors_form_phone">Telefono</string>
|
||||||
|
<string name="contractors_form_email">Email</string>
|
||||||
|
<string name="contractors_form_website">Sito web</string>
|
||||||
|
<string name="contractors_form_specialties">Specializzazioni</string>
|
||||||
|
<string name="contractors_form_address_section">Indirizzo</string>
|
||||||
|
<string name="contractors_form_street_address">Via e numero civico</string>
|
||||||
|
<string name="contractors_form_city">Città</string>
|
||||||
|
<string name="contractors_form_state">Provincia</string>
|
||||||
|
<string name="contractors_form_zip_code">CAP</string>
|
||||||
|
<string name="contractors_form_notes_section">Note</string>
|
||||||
|
<string name="contractors_form_private_notes">Note private</string>
|
||||||
|
<string name="contractors_form_mark_favorite">Segna come preferito</string>
|
||||||
|
<string name="contractors_form_add_button">Aggiungi</string>
|
||||||
|
<string name="contractors_form_save_button">Salva</string>
|
||||||
|
<string name="documents_form_edit_warranty">Modifica garanzia</string>
|
||||||
|
<string name="documents_form_edit_document">Modifica documento</string>
|
||||||
|
<string name="documents_form_add_warranty">Aggiungi garanzia</string>
|
||||||
|
<string name="documents_form_add_document">Aggiungi documento</string>
|
||||||
|
<string name="documents_form_select_residence">Seleziona residenza</string>
|
||||||
|
<string name="documents_form_residence_required">Residenza *</string>
|
||||||
|
<string name="documents_form_document_type_required">Tipo di documento *</string>
|
||||||
|
<string name="documents_form_title_required">Titolo *</string>
|
||||||
|
<string name="documents_form_item_name_required">Nome articolo *</string>
|
||||||
|
<string name="documents_form_model_number">Numero di modello</string>
|
||||||
|
<string name="documents_form_serial_number">Numero di serie</string>
|
||||||
|
<string name="documents_form_provider_required">Fornitore/Azienda *</string>
|
||||||
|
<string name="documents_form_provider_contact">Contatto del fornitore</string>
|
||||||
|
<string name="documents_form_claim_phone">Telefono per reclami</string>
|
||||||
|
<string name="documents_form_claim_email">Email per reclami</string>
|
||||||
|
<string name="documents_form_claim_website">Sito web per reclami</string>
|
||||||
|
<string name="documents_form_purchase_date">Data di acquisto (AAAA-MM-GG)</string>
|
||||||
|
<string name="documents_form_warranty_start">Data di inizio garanzia (AAAA-MM-GG)</string>
|
||||||
|
<string name="documents_form_warranty_end_required">Data di fine garanzia (AAAA-MM-GG) *</string>
|
||||||
|
<string name="documents_form_description">Descrizione</string>
|
||||||
|
<string name="documents_form_category">Categoria</string>
|
||||||
|
<string name="documents_form_select_category">Seleziona categoria</string>
|
||||||
|
<string name="documents_form_category_none">Nessuna</string>
|
||||||
|
<string name="documents_form_tags">Tag</string>
|
||||||
|
<string name="documents_form_tags_placeholder">tag1, tag2, tag3</string>
|
||||||
|
<string name="documents_form_notes">Note</string>
|
||||||
|
<string name="documents_form_active">Attivo</string>
|
||||||
|
<string name="documents_form_existing_photos">Foto esistenti (%1$d)</string>
|
||||||
|
<string name="documents_form_new_photos">Nuove foto (%1$d/%2$d)</string>
|
||||||
|
<string name="documents_form_photos">Foto (%1$d/%2$d)</string>
|
||||||
|
<string name="documents_form_camera">Fotocamera</string>
|
||||||
|
<string name="documents_form_gallery">Galleria</string>
|
||||||
|
<string name="documents_form_image_number">Immagine %1$d</string>
|
||||||
|
<string name="documents_form_remove_image">Rimuovi immagine</string>
|
||||||
|
<string name="documents_form_update_warranty">Aggiorna garanzia</string>
|
||||||
|
<string name="documents_form_update_document">Aggiorna documento</string>
|
||||||
|
<string name="documents_form_select_residence_error">Seleziona una residenza</string>
|
||||||
|
<string name="documents_form_title_error">Il titolo è obbligatorio</string>
|
||||||
|
<string name="documents_form_item_name_error">Il nome dell\'articolo è obbligatorio per le garanzie</string>
|
||||||
|
<string name="documents_form_provider_error">Il fornitore è obbligatorio per le garanzie</string>
|
||||||
|
<string name="documents_form_date_placeholder">2024-01-15</string>
|
||||||
|
<string name="documents_form_date_placeholder_end">2025-01-15</string>
|
||||||
|
<string name="documents_form_failed_to_load_residences">Caricamento delle residenze non riuscito: %1$s</string>
|
||||||
|
<string name="profile_support">Assistenza</string>
|
||||||
|
<string name="profile_contact_support">Contatta l\'assistenza</string>
|
||||||
|
<string name="profile_contact_support_subtitle">Ricevi aiuto per il tuo account</string>
|
||||||
|
<string name="profile_upgrade_benefits_title">Sblocca le funzioni Premium</string>
|
||||||
|
<string name="profile_upgrade_benefits_subtitle">Passa a Pro per l\'esperienza completa</string>
|
||||||
|
<string name="profile_benefit_unlimited_properties">Proprietà illimitate</string>
|
||||||
|
<string name="profile_benefit_document_vault">Archiviazione documenti e garanzie</string>
|
||||||
|
<string name="profile_benefit_residence_sharing">Condivisione residenze</string>
|
||||||
|
<string name="profile_benefit_contractor_sharing">Condivisione fornitori</string>
|
||||||
|
<string name="profile_benefit_actionable_notifications">Notifiche interattive</string>
|
||||||
|
<string name="profile_benefit_widgets">Widget per la schermata Home</string>
|
||||||
|
<string name="profile_privacy">Informativa sulla privacy</string>
|
||||||
|
<string name="profile_privacy_subtitle">Visualizza la nostra informativa sulla privacy</string>
|
||||||
|
<string name="profile_app_version">Versione %1$s</string>
|
||||||
|
<string name="profile_app_name">honeyDue</string>
|
||||||
|
<string name="profile_edit_profile">Modifica profilo</string>
|
||||||
|
<string name="delete_account_title">Elimina account</string>
|
||||||
|
<string name="delete_account_subtitle">Elimina definitivamente il tuo account</string>
|
||||||
|
<string name="delete_account_warning">Questa azione è permanente e non può essere annullata. Tutti i tuoi dati verranno eliminati.</string>
|
||||||
|
<string name="delete_account_shared_warning">Anche le residenze di tua proprietà condivise con altri utenti verranno eliminate.</string>
|
||||||
|
<string name="delete_account_confirm_password">Inserisci la password per confermare</string>
|
||||||
|
<string name="delete_account_confirm_type">Digita ELIMINA per confermare</string>
|
||||||
|
<string name="delete_account_button">Elimina il mio account</string>
|
||||||
|
<string name="delete_account_cancel">Annulla</string>
|
||||||
|
<string name="delete_account_success">Account eliminato correttamente</string>
|
||||||
|
<string name="delete_account_failed">Eliminazione dell\'account non riuscita</string>
|
||||||
|
<string name="notifications_daily_digest">Riepilogo giornaliero</string>
|
||||||
|
<string name="notifications_daily_digest_desc">Panoramica giornaliera delle attività in scadenza e scadute</string>
|
||||||
|
<string name="notifications_email_section">Notifiche email</string>
|
||||||
|
<string name="notifications_email_task_completed">Email attività completata</string>
|
||||||
|
<string name="notifications_email_task_completed_desc">Ricevi un\'email quando un\'attività viene completata</string>
|
||||||
|
<string name="notifications_set_custom_time">Imposta orario personalizzato</string>
|
||||||
|
<string name="notifications_change_time">Modifica</string>
|
||||||
|
<string name="notifications_select_time">Seleziona orario notifica</string>
|
||||||
|
<string name="notifications_master_title">Tutte le notifiche</string>
|
||||||
|
<string name="notifications_master_desc">Attiva o disattiva ogni categoria con un tocco</string>
|
||||||
|
<string name="notifications_categories_section">Categorie</string>
|
||||||
|
<string name="notifications_category_task_reminder">Promemoria attività</string>
|
||||||
|
<string name="notifications_category_task_reminder_desc">Promemoria per attività imminenti e in scadenza</string>
|
||||||
|
<string name="notifications_category_task_overdue">Attività scadute</string>
|
||||||
|
<string name="notifications_category_task_overdue_desc">Avvisi quando un\'attività supera la data di scadenza</string>
|
||||||
|
<string name="notifications_category_residence_invite">Inviti alle residenze</string>
|
||||||
|
<string name="notifications_category_residence_invite_desc">Inviti a unirsi a una residenza condivisa</string>
|
||||||
|
<string name="notifications_category_subscription">Aggiornamenti abbonamento</string>
|
||||||
|
<string name="notifications_category_subscription_desc">Modifiche alla fatturazione e allo stato del piano</string>
|
||||||
|
<string name="notifications_open_system_settings">Apri le impostazioni di sistema</string>
|
||||||
|
<string name="notifications_system_settings_desc">Regola suoni, badge e Non disturbare nelle impostazioni di Android</string>
|
||||||
|
<string name="common_share">Condividi</string>
|
||||||
|
<string name="common_import">Importa</string>
|
||||||
|
<string name="common_importing">Importazione in corso...</string>
|
||||||
|
<string name="common_try_again">Riprova</string>
|
||||||
|
<string name="home_overdue">Scadute</string>
|
||||||
|
<string name="home_due_this_week">In scadenza questa settimana</string>
|
||||||
|
<string name="home_next_30_days">Prossimi 30 giorni</string>
|
||||||
|
<string name="home_your_properties">Le tue proprietà</string>
|
||||||
|
<string name="onboarding_welcome_title">Benvenuto in honeyDue</string>
|
||||||
|
<string name="onboarding_welcome_subtitle">Il tuo compagno per la manutenzione della casa</string>
|
||||||
|
<string name="onboarding_start_fresh">Inizia da zero</string>
|
||||||
|
<string name="onboarding_join_existing">Unisciti a una casa esistente</string>
|
||||||
|
<string name="onboarding_already_have_account">Hai già un account? Accedi</string>
|
||||||
|
<string name="onboarding_skip">Salta</string>
|
||||||
|
<string name="onboarding_continue">Continua</string>
|
||||||
|
<string name="onboarding_get_started">Inizia</string>
|
||||||
|
<string name="onboarding_feature_tasks_title">Non dimenticare mai un\'attività</string>
|
||||||
|
<string name="onboarding_feature_tasks_desc">Tieni traccia di tutte le attività di manutenzione della casa in un unico posto con promemoria intelligenti</string>
|
||||||
|
<string name="onboarding_feature_docs_title">Documenti a portata di mano</string>
|
||||||
|
<string name="onboarding_feature_docs_desc">Conserva garanzie, manuali e ricevute in modo sicuro e accedi quando vuoi</string>
|
||||||
|
<string name="onboarding_feature_contractors_title">I tuoi fornitori di fiducia</string>
|
||||||
|
<string name="onboarding_feature_contractors_desc">Tieni tutti i contatti dei fornitori organizzati e facilmente accessibili</string>
|
||||||
|
<string name="onboarding_feature_family_title">Condividi con la famiglia</string>
|
||||||
|
<string name="onboarding_feature_family_desc">Invita i familiari a collaborare insieme alla manutenzione della casa</string>
|
||||||
|
<string name="onboarding_feature_notifications_title">Notifiche intelligenti</string>
|
||||||
|
<string name="onboarding_feature_notifications_desc">Ricevi promemoria interattivi che ti permettono di completare le attività direttamente dalla notifica</string>
|
||||||
|
<string name="onboarding_feature_widgets_title">Widget per la schermata Home</string>
|
||||||
|
<string name="onboarding_feature_widgets_desc">Accesso rapido ad attività e promemoria direttamente dalla schermata Home</string>
|
||||||
|
<string name="onboarding_location_title">Dove si trova la tua casa?</string>
|
||||||
|
<string name="onboarding_location_subtitle">Ti suggeriremo attività di manutenzione specifiche per il clima della tua zona</string>
|
||||||
|
<string name="onboarding_location_use_my_location">Usa la mia posizione</string>
|
||||||
|
<string name="onboarding_location_detecting">Rilevamento in corso...</string>
|
||||||
|
<string name="onboarding_location_enter_zip">Inserisci invece il CAP</string>
|
||||||
|
<string name="onboarding_location_enter_zip_prompt">Inserisci il tuo CAP</string>
|
||||||
|
<string name="onboarding_location_zip_placeholder">CAP</string>
|
||||||
|
<string name="onboarding_name_residence_title">Dai un nome alla tua casa</string>
|
||||||
|
<string name="onboarding_name_residence_subtitle">Dai un nome alla tua proprietà per identificarla più facilmente</string>
|
||||||
|
<string name="onboarding_name_residence_placeholder">es. Casa mia, Casa al mare, Appartamento</string>
|
||||||
|
<string name="onboarding_name_residence_hint">Puoi aggiungere altri dettagli in seguito</string>
|
||||||
|
<string name="onboarding_create_account_title">Salva la tua casa</string>
|
||||||
|
<string name="onboarding_create_account_subtitle">Crea un account per sincronizzare su più dispositivi</string>
|
||||||
|
<string name="onboarding_create_with_email">Crea account con email</string>
|
||||||
|
<string name="onboarding_verify_email_title">Verifica la tua email</string>
|
||||||
|
<string name="onboarding_verify_email_subtitle">Abbiamo inviato un codice di 6 cifre alla tua email. Inseriscilo qui sotto per verificare il tuo account.</string>
|
||||||
|
<string name="onboarding_verify_email_hint">Non hai ricevuto il codice? Controlla la cartella spam</string>
|
||||||
|
<string name="onboarding_join_title">Unisciti a una residenza</string>
|
||||||
|
<string name="onboarding_join_subtitle">Inserisci il codice di 6 caratteri condiviso con te per unirti a una casa esistente</string>
|
||||||
|
<string name="onboarding_join_placeholder">Inserisci il codice di condivisione</string>
|
||||||
|
<string name="onboarding_join_button">Unisciti alla residenza</string>
|
||||||
|
<string name="onboarding_tasks_title">Tutto pronto!</string>
|
||||||
|
<string name="onboarding_tasks_subtitle">Iniziamo con qualche attività. Più ne scegli, più ti aiuteremo a ricordare!</string>
|
||||||
|
<string name="onboarding_tasks_selected">%1$d/%2$d attività selezionate</string>
|
||||||
|
<string name="onboarding_tasks_add_popular">Aggiungi le più popolari</string>
|
||||||
|
<string name="onboarding_tasks_continue">Aggiungi %1$d attività e continua</string>
|
||||||
|
<string name="onboarding_tasks_skip">Salta per ora</string>
|
||||||
|
<string name="onboarding_category_hvac">Climatizzazione e riscaldamento</string>
|
||||||
|
<string name="onboarding_category_safety">Sicurezza e protezione</string>
|
||||||
|
<string name="onboarding_category_plumbing">Impianto idraulico</string>
|
||||||
|
<string name="onboarding_category_outdoor">Esterni e giardino</string>
|
||||||
|
<string name="onboarding_category_appliances">Elettrodomestici</string>
|
||||||
|
<string name="onboarding_category_general">Casa in generale</string>
|
||||||
|
<string name="onboarding_subscription_title">Passa a Pro</string>
|
||||||
|
<string name="onboarding_subscription_subtitle">Porta la gestione della tua casa a un livello superiore</string>
|
||||||
|
<string name="onboarding_subscription_pro">HONEYDUE PRO</string>
|
||||||
|
<string name="onboarding_subscription_social_proof">4,9 • oltre 10.000 proprietari di casa</string>
|
||||||
|
<string name="onboarding_subscription_benefit_properties">Proprietà illimitate</string>
|
||||||
|
<string name="onboarding_subscription_benefit_properties_desc">Tieni traccia di ogni casa che possiedi</string>
|
||||||
|
<string name="onboarding_subscription_benefit_reminders">Promemoria intelligenti</string>
|
||||||
|
<string name="onboarding_subscription_benefit_reminders_desc">Non perdere mai una scadenza di manutenzione</string>
|
||||||
|
<string name="onboarding_subscription_benefit_documents">Archivio documenti</string>
|
||||||
|
<string name="onboarding_subscription_benefit_documents_desc">Tutti i tuoi documenti in un unico posto</string>
|
||||||
|
<string name="onboarding_subscription_benefit_family">Condivisione familiare</string>
|
||||||
|
<string name="onboarding_subscription_benefit_family_desc">Tieni tutti aggiornati allo stesso modo</string>
|
||||||
|
<string name="onboarding_subscription_benefit_insights">Analisi delle spese</string>
|
||||||
|
<string name="onboarding_subscription_benefit_insights_desc">Scopri dove vanno a finire i tuoi soldi</string>
|
||||||
|
<string name="onboarding_subscription_benefit_contractor_sharing">Condivisione fornitori</string>
|
||||||
|
<string name="onboarding_subscription_benefit_contractor_sharing_desc">Condividi i tuoi fornitori di fiducia con amici e familiari</string>
|
||||||
|
<string name="onboarding_subscription_benefit_widgets">Widget per la schermata Home</string>
|
||||||
|
<string name="onboarding_subscription_benefit_widgets_desc">Azioni rapide direttamente dalla schermata Home</string>
|
||||||
|
<string name="onboarding_subscription_benefit_notifications">Notifiche interattive</string>
|
||||||
|
<string name="onboarding_subscription_benefit_notifications_desc">Completa le attività direttamente dalle notifiche</string>
|
||||||
|
<string name="onboarding_subscription_choose_plan">Scegli il tuo piano</string>
|
||||||
|
<string name="onboarding_subscription_monthly">Mensile</string>
|
||||||
|
<string name="onboarding_subscription_yearly">Annuale</string>
|
||||||
|
<string name="onboarding_subscription_save">Risparmia il 30%</string>
|
||||||
|
<string name="onboarding_subscription_monthly_price">2,99 $/mese</string>
|
||||||
|
<string name="onboarding_subscription_yearly_price">23,99 $/anno</string>
|
||||||
|
<string name="onboarding_subscription_yearly_monthly">Solo 1,99 $/mese</string>
|
||||||
|
<string name="onboarding_subscription_start_trial">Inizia la prova gratuita di 7 giorni</string>
|
||||||
|
<string name="onboarding_subscription_continue_free">Continua con la versione gratuita</string>
|
||||||
|
<string name="onboarding_subscription_trial_terms">Prova gratuita di 7 giorni, poi %1$s. Annulla quando vuoi.</string>
|
||||||
|
<string name="onboarding_home_profile_title">Parlaci della tua casa</string>
|
||||||
|
<string name="onboarding_home_profile_subtitle">Tutto facoltativo: ci aiuta a personalizzare il tuo piano di manutenzione</string>
|
||||||
|
<string name="onboarding_home_profile_systems">Impianti</string>
|
||||||
|
<string name="onboarding_home_profile_features">Caratteristiche</string>
|
||||||
|
<string name="onboarding_home_profile_exterior">Esterni</string>
|
||||||
|
<string name="onboarding_home_profile_interior">Interni</string>
|
||||||
|
<string name="for_you_tab">Per te</string>
|
||||||
|
<string name="browse_tab">Sfoglia</string>
|
||||||
|
<string name="biometric_lock_title">App bloccata</string>
|
||||||
|
<string name="biometric_lock_description">Autenticati per sbloccare honeyDue</string>
|
||||||
|
<string name="biometric_lock_setting_title">Blocco biometrico</string>
|
||||||
|
<string name="biometric_lock_setting_subtitle">Richiedi l\'autenticazione all\'apertura dell\'app</string>
|
||||||
|
<string name="biometric_prompt_title">Sblocca honeyDue</string>
|
||||||
|
<string name="biometric_prompt_subtitle">Verifica la tua identità per continuare</string>
|
||||||
|
<string name="biometric_unlock_button">Sblocca con dati biometrici</string>
|
||||||
|
<string name="biometric_auth_failed">Autenticazione non riuscita</string>
|
||||||
|
<string name="biometric_not_available">L\'autenticazione biometrica non è disponibile su questo dispositivo</string>
|
||||||
|
<string name="notif_channel_task_reminder_name">Promemoria attività</string>
|
||||||
|
<string name="notif_channel_task_reminder_description">Promemoria per attività imminenti e in scadenza</string>
|
||||||
|
<string name="notif_channel_task_overdue_name">Attività scadute</string>
|
||||||
|
<string name="notif_channel_task_overdue_description">Avvisi quando un\'attività supera la data di scadenza</string>
|
||||||
|
<string name="notif_channel_residence_invite_name">Inviti alle residenze</string>
|
||||||
|
<string name="notif_channel_residence_invite_description">Inviti a unirsi a una residenza condivisa</string>
|
||||||
|
<string name="notif_channel_subscription_name">Aggiornamenti abbonamento</string>
|
||||||
|
<string name="notif_channel_subscription_description">Aggiornamenti sullo stato dell\'abbonamento e sulla fatturazione</string>
|
||||||
|
<string name="common_selected">Selezionato</string>
|
||||||
|
<string name="common_open">Apri</string>
|
||||||
|
<string name="common_skip">Salta</string>
|
||||||
|
<string name="common_or">o</string>
|
||||||
|
<string name="common_info">Info</string>
|
||||||
|
<string name="common_verified">Verificato</string>
|
||||||
|
<string name="common_warning">Avviso</string>
|
||||||
|
<string name="common_photo">Foto</string>
|
||||||
|
<string name="common_included">Incluso</string>
|
||||||
|
<string name="common_verifying">Verifica in corso…</string>
|
||||||
|
<string name="time_am">AM</string>
|
||||||
|
<string name="time_pm">PM</string>
|
||||||
|
<string name="time_eve">SERA</string>
|
||||||
|
<string name="error_network_title">Errore di rete</string>
|
||||||
|
<string name="image_failed_to_load">Caricamento non riuscito</string>
|
||||||
|
<string name="theme_appearance_title">Aspetto</string>
|
||||||
|
<string name="theme_use_system_colors">Usa i colori di sistema</string>
|
||||||
|
<string name="theme_material_you_desc">Segui Material You di Android 12+ (colori dello sfondo)</string>
|
||||||
|
<string name="paywall_choose_plan_title">Scegli il tuo piano</string>
|
||||||
|
<string name="paywall_choose_plan_subtitle">Passa a Pro per un accesso illimitato</string>
|
||||||
|
<string name="paywall_upgrade_to_pro">Passa a Pro</string>
|
||||||
|
<string name="paywall_col_feature">Funzione</string>
|
||||||
|
<string name="paywall_col_free">Gratis</string>
|
||||||
|
<string name="paywall_col_pro">Pro</string>
|
||||||
|
<string name="paywall_feat_properties">Proprietà</string>
|
||||||
|
<string name="paywall_feat_tasks">Attività</string>
|
||||||
|
<string name="paywall_feat_contractors">Appaltatori</string>
|
||||||
|
<string name="paywall_feat_documents">Documenti</string>
|
||||||
|
<string name="paywall_val_1_property">1 proprietà</string>
|
||||||
|
<string name="paywall_val_10_tasks">10 attività</string>
|
||||||
|
<string name="paywall_val_unlimited">Illimitato</string>
|
||||||
|
<string name="paywall_val_not_available">Non disponibile</string>
|
||||||
|
<string name="reset_check_email_title">Controlla la tua email</string>
|
||||||
|
<string name="reset_sent_code_to">Abbiamo inviato un codice di 6 cifre a</string>
|
||||||
|
<string name="reset_code_expires">Il codice scade tra 15 minuti</string>
|
||||||
|
<string name="reset_enter_code_hint">Inserisci il codice di 6 cifre ricevuto via email</string>
|
||||||
|
<string name="reset_code_verified_msg">Codice verificato! Ora imposta la tua nuova password</string>
|
||||||
|
<string name="reset_didnt_receive">Non hai ricevuto il codice?</string>
|
||||||
|
<string name="reset_check_spam">Controlla la cartella spam se non lo vedi</string>
|
||||||
|
<string name="reset_verify_failed_title">Verifica del codice non riuscita</string>
|
||||||
|
<string name="verify_email_required_msg">È richiesta la verifica dell\'email. Controlla la posta in arrivo per un codice di 6 cifre.</string>
|
||||||
|
<string name="verify_email_invalid_code">Inserisci un codice valido di 6 cifre</string>
|
||||||
|
<string name="verify_email_didnt_receive">Non hai ricevuto il codice? Controlla la cartella spam o contatta l\'assistenza.</string>
|
||||||
|
<string name="verify_email_failed_title">Verifica non riuscita</string>
|
||||||
|
<string name="reset_pw_success_msg">La tua password è stata reimpostata correttamente</string>
|
||||||
|
<string name="reset_pw_can_login">Ora puoi accedere con la tua nuova password</string>
|
||||||
|
<string name="reset_return_to_login">Torna all\'accesso</string>
|
||||||
|
<string name="reset_set_new_pw_title">Imposta nuova password</string>
|
||||||
|
<string name="reset_create_strong_pw">Crea una password sicura per proteggere il tuo account</string>
|
||||||
|
<string name="reset_pw_requirements">Requisiti della password</string>
|
||||||
|
<string name="reset_pw_failed_title">Reimpostazione della password non riuscita</string>
|
||||||
|
<string name="forgot_send_code_hint">Invieremo un codice di verifica di 6 cifre a questo indirizzo</string>
|
||||||
|
<string name="forgot_check_email_msg">Controlla la tua email per un codice di verifica di 6 cifre</string>
|
||||||
|
<string name="forgot_back_to_login">Ricordi la password? Torna all\'accesso</string>
|
||||||
|
<string name="forgot_send_failed_title">Invio del codice di reimpostazione non riuscito</string>
|
||||||
|
<string name="join_property_title">Unisciti a una proprietà</string>
|
||||||
|
<string name="join_shared_property_header">Unisciti a una proprietà condivisa</string>
|
||||||
|
<string name="join_enter_code_desc">Inserisci il codice di 6 caratteri fornito dal proprietario.</string>
|
||||||
|
<string name="join_share_code_label">Codice di condivisione</string>
|
||||||
|
<string name="join_share_code_placeholder">ABC123</string>
|
||||||
|
<string name="join_code_helper">I codici sono di 6 caratteri maiuscoli</string>
|
||||||
|
<string name="join_joining">Accesso in corso…</string>
|
||||||
|
<string name="biometric_enter_pin">Inserisci il PIN per sbloccare</string>
|
||||||
|
<string name="biometric_pin_label">PIN a 4 cifre</string>
|
||||||
|
<string name="biometric_incorrect_pin">PIN errato</string>
|
||||||
|
<string name="biometric_unlock">Sblocca</string>
|
||||||
|
<string name="tasks_create_failed_title">Creazione dell\'attività non riuscita</string>
|
||||||
|
<string name="tasks_all_title">Tutte le attività</string>
|
||||||
|
<string name="tasks_add">Aggiungi attività</string>
|
||||||
|
<string name="tasks_load_failed_title">Caricamento delle attività non riuscito</string>
|
||||||
|
<string name="tasks_all_empty_subtitle">Crea la tua prima attività per iniziare</string>
|
||||||
|
<string name="tasks_add_property_first">Aggiungi prima una proprietà dalla scheda Residenze</string>
|
||||||
|
<string name="residences_upgrade_to_add">Esegui l\'upgrade per aggiungere</string>
|
||||||
|
<string name="residences_primary_cd">Residenza principale</string>
|
||||||
|
<string name="residence_unit_label">Unità: %1$s</string>
|
||||||
|
<string name="residence_error_loading_tasks">Errore durante il caricamento delle attività: %1$s</string>
|
||||||
|
<string name="residence_error_loading_contractors">Errore durante il caricamento degli appaltatori: %1$s</string>
|
||||||
|
<string name="profile_subscription_cd">Abbonamento</string>
|
||||||
|
<string name="onboarding_sub_cancel_note">Annulla quando vuoi nelle Impostazioni • Nessun vincolo</string>
|
||||||
|
<string name="onboarding_joining_residence">Accesso alla residenza…</string>
|
||||||
|
<string name="onboarding_sub_trial_legal">Prova gratuita di 7 giorni, poi %1$s</string>
|
||||||
|
<string name="common_collapse">Comprimi</string>
|
||||||
|
<string name="common_expand">Espandi</string>
|
||||||
|
<string name="common_not_selected">Non selezionato</string>
|
||||||
|
<string name="auth_logging_in">Accesso in corso…</string>
|
||||||
|
<string name="auth_requirement_met">Requisito soddisfatto</string>
|
||||||
|
<string name="auth_requirement_not_met">Requisito non soddisfatto</string>
|
||||||
|
<string name="error_something_wrong">Si è verificato un errore</string>
|
||||||
|
<string name="paywall_feat_not_included">Non incluso</string>
|
||||||
|
<string name="completions_contractor_prefix">Appaltatore:</string>
|
||||||
|
<string name="completions_completed_by_prefix">Completato da:</string>
|
||||||
|
<string name="completions_remove_photo">Rimuovi foto</string>
|
||||||
|
<string name="completions_star_rating_cd">%1$d stelle</string>
|
||||||
|
<string name="photos_completion_title">Foto di completamento</string>
|
||||||
|
<string name="photos_task_completion_cd">Foto di completamento dell\'attività</string>
|
||||||
|
<string name="documents_residence_ref">Residenza n. %1$d</string>
|
||||||
|
<string name="documents_task_ref">Attività n. %1$d</string>
|
||||||
|
<string name="documents_image_index">Immagine %1$d di %2$d</string>
|
||||||
|
<string name="documents_images_title">Immagini del documento</string>
|
||||||
|
<string name="documents_empty_no_warranties">Nessuna garanzia trovata</string>
|
||||||
|
<string name="documents_empty_no_documents">Nessun documento trovato</string>
|
||||||
|
<string name="documents_days_remaining_count">%1$d giorni rimanenti</string>
|
||||||
|
<string name="contractors_property_ref">Proprietà n.</string>
|
||||||
|
<string name="residence_share_failed">Condivisione della residenza non riuscita</string>
|
||||||
|
<string name="theme_default">Predefinito</string>
|
||||||
|
<string name="theme_default_desc">Vivaci colori di sistema iOS</string>
|
||||||
|
<string name="theme_teal">Foglia di tè</string>
|
||||||
|
<string name="theme_teal_desc">Blu-verde con accenti caldi</string>
|
||||||
|
<string name="theme_ocean">Oceano</string>
|
||||||
|
<string name="theme_ocean_desc">Blu profondi e tonalità corallo</string>
|
||||||
|
<string name="theme_forest">Foresta</string>
|
||||||
|
<string name="theme_forest_desc">Verdi terra e sfumature dorate</string>
|
||||||
|
<string name="theme_sunset">Tramonto</string>
|
||||||
|
<string name="theme_sunset_desc">Caldi aranci e rossi</string>
|
||||||
|
<string name="theme_monochrome">Monocromatico</string>
|
||||||
|
<string name="theme_monochrome_desc">Elegante scala di grigi</string>
|
||||||
|
<string name="theme_lavender">Lavanda</string>
|
||||||
|
<string name="theme_lavender_desc">Viola tenue con accenti rosa</string>
|
||||||
|
<string name="theme_crimson">Cremisi</string>
|
||||||
|
<string name="theme_crimson_desc">Rosso intenso con riflessi caldi</string>
|
||||||
|
<string name="theme_midnight">Mezzanotte</string>
|
||||||
|
<string name="theme_midnight_desc">Blu navy profondo con azzurro cielo</string>
|
||||||
|
<string name="theme_desert">Deserto</string>
|
||||||
|
<string name="theme_desert_desc">Calde tonalità di terracotta e sabbia</string>
|
||||||
|
<string name="theme_mint">Menta</string>
|
||||||
|
<string name="theme_mint_desc">Verde fresco con turchese</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -472,4 +472,585 @@
|
|||||||
<string name="subscription_features">プレミアム機能</string>
|
<string name="subscription_features">プレミアム機能</string>
|
||||||
<string name="subscription_limit_properties">プランの物件数上限に達しました</string>
|
<string name="subscription_limit_properties">プランの物件数上限に達しました</string>
|
||||||
<string name="subscription_limit_tasks">プランのタスク数上限に達しました</string>
|
<string name="subscription_limit_tasks">プランのタスク数上限に達しました</string>
|
||||||
|
<string name="home_profile_heating">暖房</string>
|
||||||
|
<string name="home_profile_cooling">冷房</string>
|
||||||
|
<string name="home_profile_water_heater">給湯器</string>
|
||||||
|
<string name="home_profile_pool">プール</string>
|
||||||
|
<string name="home_profile_sprinkler_system">スプリンクラー</string>
|
||||||
|
<string name="home_profile_fireplace">暖炉</string>
|
||||||
|
<string name="home_profile_garage">ガレージ</string>
|
||||||
|
<string name="home_profile_basement">地下室</string>
|
||||||
|
<string name="home_profile_attic">屋根裏</string>
|
||||||
|
<string name="home_profile_septic">浄化槽</string>
|
||||||
|
<string name="home_profile_roof_type">屋根の種類</string>
|
||||||
|
<string name="home_profile_exterior">外装</string>
|
||||||
|
<string name="home_profile_flooring">床材</string>
|
||||||
|
<string name="home_profile_landscaping">造園</string>
|
||||||
|
<string name="onboarding_first_task_selected_count">%1$d件のタスクを選択中</string>
|
||||||
|
<string name="onboarding_first_task_add_continue">%1$d件のタスクを追加して続行</string>
|
||||||
|
<string name="onboarding_first_task_finding">ご自宅に合うタスクを検索中...</string>
|
||||||
|
<string name="onboarding_first_task_no_suggestions">おすすめはまだありません。カタログを見るか、このステップをスキップしてください。</string>
|
||||||
|
<string name="onboarding_first_task_browse_all">すべて見る</string>
|
||||||
|
<string name="onboarding_first_task_skip">スキップ</string>
|
||||||
|
<string name="onboarding_first_task_suggestions_error">おすすめを読み込めませんでした</string>
|
||||||
|
<string name="onboarding_first_task_connection_error">接続を確認してもう一度お試しください。</string>
|
||||||
|
<string name="onboarding_first_task_loading_catalog">タスクカタログを読み込み中...</string>
|
||||||
|
<string name="onboarding_first_task_catalog_error">タスクカタログを読み込めませんでした</string>
|
||||||
|
<string name="onboarding_first_task_no_templates">現在利用できるテンプレートはありません。</string>
|
||||||
|
<string name="onboarding_first_task_selected">選択済み</string>
|
||||||
|
<string name="onboarding_first_task_offline">オフライン</string>
|
||||||
|
<string name="onboarding_first_task_skip_for_now">後でスキップ</string>
|
||||||
|
<string name="upgrade_hero_title">Proにアップグレード</string>
|
||||||
|
<string name="upgrade_hero_subtitle">honeyDueをフル活用しましょう</string>
|
||||||
|
<string name="upgrade_choose_plan">プランを選択</string>
|
||||||
|
<string name="upgrade_plan_yearly">年額</string>
|
||||||
|
<string name="upgrade_plan_monthly">月額</string>
|
||||||
|
<string name="upgrade_plan_save_50">50%お得</string>
|
||||||
|
<string name="upgrade_best_value">一番お得</string>
|
||||||
|
<string name="upgrade_whats_included">含まれる内容</string>
|
||||||
|
<string name="upgrade_feature_unlimited_properties">物件数無制限</string>
|
||||||
|
<string name="upgrade_feature_unlimited_properties_desc">すべての住まいのメンテナンスを管理</string>
|
||||||
|
<string name="upgrade_feature_unlimited_tasks">タスク数無制限</string>
|
||||||
|
<string name="upgrade_feature_unlimited_tasks_desc">メンテナンスタスクを忘れません</string>
|
||||||
|
<string name="upgrade_feature_contractor_management">業者管理</string>
|
||||||
|
<string name="upgrade_feature_contractor_management_desc">信頼できる業者を保存・評価</string>
|
||||||
|
<string name="upgrade_feature_document_vault">ドキュメント保管庫</string>
|
||||||
|
<string name="upgrade_feature_document_vault_desc">保証書、領収書、説明書を保管</string>
|
||||||
|
<string name="upgrade_feature_family_sharing">家族共有</string>
|
||||||
|
<string name="upgrade_feature_family_sharing_desc">家族を招待して共同管理</string>
|
||||||
|
<string name="upgrade_feature_smart_reminders">スマートリマインダー</string>
|
||||||
|
<string name="upgrade_feature_smart_reminders_desc">タスクの期限を通知</string>
|
||||||
|
<string name="upgrade_feature_document_warranty_storage">ドキュメント・保証書の保管</string>
|
||||||
|
<string name="upgrade_subscribe_now">今すぐ登録</string>
|
||||||
|
<string name="upgrade_restore_purchases">購入を復元</string>
|
||||||
|
<string name="upgrade_terms_text">サブスクリプションは、現在の期間終了の24時間前までに解約しない限り自動更新されます。サブスクリプションはデバイスの設定で管理できます。</string>
|
||||||
|
<string name="upgrade_terms_of_use">利用規約</string>
|
||||||
|
<string name="upgrade_included">含まれます</string>
|
||||||
|
<string name="upgrade_compare_free_vs_pro">無料版とProを比較</string>
|
||||||
|
<string name="upgrade_maybe_later">後で</string>
|
||||||
|
<string name="upgrade_warning">警告</string>
|
||||||
|
<string name="upgrade_subscription_active">サブスクリプション有効</string>
|
||||||
|
<string name="upgrade_subscription_active_message">すべてのPro機能をご利用いただけるようになりました!</string>
|
||||||
|
<string name="upgrade_feature_required_title">アップグレードが必要</string>
|
||||||
|
<string name="upgrade_feature_required_message">この機能はProプランでご利用いただけます。</string>
|
||||||
|
<string name="upgrade_prompt_default_message">すべての機能を無制限に利用</string>
|
||||||
|
<string name="upgrade_product_monthly_name">honeyDue Pro 月額</string>
|
||||||
|
<string name="upgrade_product_annual_name">honeyDue Pro 年額</string>
|
||||||
|
<string name="upgrade_billed_monthly">月額請求</string>
|
||||||
|
<string name="upgrade_billed_annually">年額請求</string>
|
||||||
|
<string name="upgrade_save_22">22%お得</string>
|
||||||
|
<string name="tasks_column_done">完了</string>
|
||||||
|
<string name="tasks_column_archived">アーカイブ済み</string>
|
||||||
|
<string name="tasks_column_empty">タスクなし</string>
|
||||||
|
<string name="tasks_new_title">新規タスク</string>
|
||||||
|
<string name="tasks_title_field_label">タイトル</string>
|
||||||
|
<string name="tasks_title_placeholder">例:給湯器の水抜き</string>
|
||||||
|
<string name="tasks_description_placeholder">詳細(任意)</string>
|
||||||
|
<string name="tasks_due_date_optional_label">期限(任意)</string>
|
||||||
|
<string name="tasks_due_date_placeholder_format">yyyy-MM-dd</string>
|
||||||
|
<string name="tasks_due_date_blank_helper">期限なしの場合は空欄のまま</string>
|
||||||
|
<string name="tasks_estimated_cost_optional_label">概算費用(任意)</string>
|
||||||
|
<string name="suggestions_title">おすすめのタスク</string>
|
||||||
|
<string name="suggestions_skip">スキップ</string>
|
||||||
|
<string name="suggestions_accept">追加</string>
|
||||||
|
<string name="suggestions_load_failed">おすすめを読み込めませんでした</string>
|
||||||
|
<string name="suggestions_empty_title">おすすめはまだありません</string>
|
||||||
|
<string name="suggestions_empty_subtitle">ホームプロフィールを完成させると、おすすめが表示されます。</string>
|
||||||
|
<string name="completion_history_title">完了履歴</string>
|
||||||
|
<string name="completion_history_count_one">完了%1$d件</string>
|
||||||
|
<string name="completion_history_count_other">完了%1$d件</string>
|
||||||
|
<string name="completion_history_loading">完了履歴を読み込み中...</string>
|
||||||
|
<string name="completion_history_load_failed">完了履歴の読み込みに失敗しました</string>
|
||||||
|
<string name="completion_history_empty_title">完了履歴はまだありません</string>
|
||||||
|
<string name="completion_history_empty_message">このタスクはまだ完了していません。</string>
|
||||||
|
<string name="completion_history_completed_by">%1$sが完了</string>
|
||||||
|
<string name="completion_history_view_photo">写真を表示</string>
|
||||||
|
<string name="manage_users_remove_user">ユーザーを削除</string>
|
||||||
|
<string name="manage_users_copy_code">コードをコピー</string>
|
||||||
|
<string name="manage_users_code_copied">コードをクリップボードにコピーしました</string>
|
||||||
|
<string name="manage_users_load_failed">ユーザーを読み込めませんでした</string>
|
||||||
|
<string name="manage_users_remove_confirm">%1$sをこの物件から削除しますか?</string>
|
||||||
|
<string name="properties_shared_users_count">共有ユーザー(%1$d)</string>
|
||||||
|
<string name="properties_no_shared_users">共有ユーザーなし</string>
|
||||||
|
<string name="properties_shared_users_helper">この住居にアクセスできるユーザーです。共有ボタンから他の人を招待できます。</string>
|
||||||
|
<string name="properties_remove_user_confirm">%1$sをこの住居から削除してもよろしいですか?</string>
|
||||||
|
<string name="properties_remove_button">削除</string>
|
||||||
|
<string name="documents_expires_label">有効期限</string>
|
||||||
|
<string name="auth_password_requirement_length">8文字以上</string>
|
||||||
|
<string name="auth_password_requirement_uppercase">大文字を含む</string>
|
||||||
|
<string name="auth_password_requirement_lowercase">小文字を含む</string>
|
||||||
|
<string name="auth_password_requirement_digit">数字を含む</string>
|
||||||
|
<string name="auth_password_requirement_match">パスワードが一致</string>
|
||||||
|
<string name="auth_password_requirements_title">パスワードの要件</string>
|
||||||
|
<string name="auth_password_complexity_error">パスワードは8文字以上で、大文字・小文字・数字をそれぞれ1文字以上含める必要があります</string>
|
||||||
|
<string name="properties_join_residence_title">住居に参加</string>
|
||||||
|
<string name="properties_join_residence_message">この共有住居に参加しますか?</string>
|
||||||
|
<string name="properties_join_success">住居に参加しました</string>
|
||||||
|
<string name="properties_join_success_message">%1$s にアクセスできるようになりました。</string>
|
||||||
|
<string name="properties_join_failed">参加に失敗しました</string>
|
||||||
|
<string name="properties_joining">参加中...</string>
|
||||||
|
<string name="properties_shared_by">共有者: %1$s</string>
|
||||||
|
<string name="properties_expires">有効期限: %1$s</string>
|
||||||
|
<string name="properties_share_upgrade_title">Pro機能</string>
|
||||||
|
<string name="properties_share_upgrade_message">住居の共有はPro機能です。アップグレードして家族を招待し、住まいのメンテナンスを一緒に管理しましょう。</string>
|
||||||
|
<string name="tasks_failed_to_cancel">タスクのキャンセルに失敗しました</string>
|
||||||
|
<string name="tasks_failed_to_restore">タスクの復元に失敗しました</string>
|
||||||
|
<string name="tasks_failed_to_mark_in_progress">タスクを進行中にできませんでした</string>
|
||||||
|
<string name="tasks_failed_to_archive">タスクのアーカイブに失敗しました</string>
|
||||||
|
<string name="tasks_failed_to_unarchive">タスクのアーカイブ解除に失敗しました</string>
|
||||||
|
<string name="tasks_card_in_progress">進行中</string>
|
||||||
|
<string name="tasks_card_actions">操作</string>
|
||||||
|
<string name="tasks_card_mark_in_progress">進行中にする</string>
|
||||||
|
<string name="tasks_card_complete_task">タスクを完了</string>
|
||||||
|
<string name="tasks_card_edit_task">タスクを編集</string>
|
||||||
|
<string name="tasks_card_cancel_task">タスクをキャンセル</string>
|
||||||
|
<string name="tasks_card_restore_task">タスクを復元</string>
|
||||||
|
<string name="tasks_card_archive_task">タスクをアーカイブ</string>
|
||||||
|
<string name="tasks_card_unarchive_task">アーカイブを解除</string>
|
||||||
|
<string name="tasks_card_not_available">なし</string>
|
||||||
|
<string name="tasks_card_completed_by">担当: %1$s</string>
|
||||||
|
<string name="tasks_card_cost">費用: $%1$s</string>
|
||||||
|
<string name="tasks_card_view_photos">写真を見る (%1$d)</string>
|
||||||
|
<string name="tasks_add_new">新しいタスクを追加</string>
|
||||||
|
<string name="tasks_property_required">物件 *</string>
|
||||||
|
<string name="tasks_property_error">物件は必須です</string>
|
||||||
|
<string name="tasks_browse_templates">タスクテンプレートを見る</string>
|
||||||
|
<string name="tasks_common_tasks">よく使うタスク %1$d 件</string>
|
||||||
|
<string name="tasks_category_error">カテゴリは必須です</string>
|
||||||
|
<string name="tasks_interval_days">間隔(日数)</string>
|
||||||
|
<string name="tasks_interval_override">デフォルトの頻度間隔を上書き</string>
|
||||||
|
<string name="tasks_custom_interval_help">各回の間隔の日数</string>
|
||||||
|
<string name="tasks_due_date_format_error">期日は必須です(形式: YYYY-MM-DD)</string>
|
||||||
|
<string name="tasks_due_date_format">形式: YYYY-MM-DD</string>
|
||||||
|
<string name="tasks_create">タスクを作成</string>
|
||||||
|
<string name="tasks_in_progress_label">進行中</string>
|
||||||
|
<string name="templates_title">タスクテンプレート</string>
|
||||||
|
<string name="templates_done">完了</string>
|
||||||
|
<string name="templates_search_placeholder">テンプレートを検索...</string>
|
||||||
|
<string name="templates_clear">クリア</string>
|
||||||
|
<string name="templates_result">件</string>
|
||||||
|
<string name="templates_results">件</string>
|
||||||
|
<string name="templates_no_results_title">テンプレートが見つかりません</string>
|
||||||
|
<string name="templates_no_results_message">別の検索語をお試しください</string>
|
||||||
|
<string name="templates_empty_title">テンプレートがありません</string>
|
||||||
|
<string name="templates_empty_message">読み込まれるとここに表示されます</string>
|
||||||
|
<string name="templates_expand">展開</string>
|
||||||
|
<string name="templates_collapse">折りたたむ</string>
|
||||||
|
<string name="templates_add">追加</string>
|
||||||
|
<string name="templates_all_categories">すべて</string>
|
||||||
|
<string name="templates_apply">適用</string>
|
||||||
|
<string name="templates_apply_count">適用 (%1$d)</string>
|
||||||
|
<string name="templates_selected_count">%1$d 件選択中</string>
|
||||||
|
<string name="templates_retry">再試行</string>
|
||||||
|
<string name="templates_load_failed">テンプレートの読み込みに失敗しました</string>
|
||||||
|
<string name="templates_create_failed">タスクの作成に失敗しました</string>
|
||||||
|
<string name="completions_complete_task_title">タスクを完了: %1$s</string>
|
||||||
|
<string name="completions_select_contractor">業者を選択(任意)</string>
|
||||||
|
<string name="completions_choose_contractor_placeholder">業者を選択するか空欄のままに</string>
|
||||||
|
<string name="completions_expand">展開</string>
|
||||||
|
<string name="completions_none_manual">なし(手動入力)</string>
|
||||||
|
<string name="completions_loading_contractors">業者を読み込み中...</string>
|
||||||
|
<string name="completions_error_loading_contractors">業者の読み込みエラー</string>
|
||||||
|
<string name="completions_completed_by_name">担当者名(任意)</string>
|
||||||
|
<string name="completions_completed_by_placeholder">上記の業者を使わない場合は名前を入力</string>
|
||||||
|
<string name="completions_actual_cost_optional">実費(任意)</string>
|
||||||
|
<string name="completions_notes_optional">メモ(任意)</string>
|
||||||
|
<string name="completions_rating">評価: 5段階中 %1$d</string>
|
||||||
|
<string name="completions_add_images">画像を追加</string>
|
||||||
|
<string name="completions_take_photo">写真を撮る</string>
|
||||||
|
<string name="completions_choose_from_library">ライブラリから選択</string>
|
||||||
|
<string name="completions_images_selected">%1$d 枚選択中</string>
|
||||||
|
<string name="completions_remove_image">画像を削除</string>
|
||||||
|
<string name="completions_complete_button">完了</string>
|
||||||
|
<string name="completions_quality_rating">品質評価</string>
|
||||||
|
<string name="completions_photos_count">写真 (%1$d/%2$d)</string>
|
||||||
|
<string name="completions_camera">カメラ</string>
|
||||||
|
<string name="completions_library">ライブラリ</string>
|
||||||
|
<string name="completions_add_photos_helper">完了した作業の写真を追加(任意)</string>
|
||||||
|
<string name="completions_contractor_helper">このタスク完了を業者に紐付け</string>
|
||||||
|
<string name="completions_details_section">完了の詳細</string>
|
||||||
|
<string name="completions_optional_info">すべての項目は任意です</string>
|
||||||
|
<string name="completions_notes_helper">完了した作業についてのメモを追加</string>
|
||||||
|
<string name="completions_notes_placeholder">行った作業や見つかった問題などを記入</string>
|
||||||
|
<string name="completions_rate_quality">実施した作業の品質を評価</string>
|
||||||
|
<string name="completions_enter_manually">下に名前を手動で入力</string>
|
||||||
|
<string name="manage_users_title">ユーザー管理</string>
|
||||||
|
<string name="manage_users_invite_title">他のユーザーを招待</string>
|
||||||
|
<string name="manage_users_easy_share">かんたん共有</string>
|
||||||
|
<string name="manage_users_send_invite">招待リンクを送信</string>
|
||||||
|
<string name="manage_users_easy_share_desc">メッセージ・メールなどで .honeydue ファイルを送信。タップするだけで参加できます。</string>
|
||||||
|
<string name="manage_users_share_code">共有コード</string>
|
||||||
|
<string name="manage_users_no_code">有効なコードなし</string>
|
||||||
|
<string name="manage_users_generate">コードを生成</string>
|
||||||
|
<string name="manage_users_generate_new">新しいコードを生成</string>
|
||||||
|
<string name="manage_users_code_desc">この6文字のコードを共有してください。アプリで入力すると参加できます。</string>
|
||||||
|
<string name="manage_users_users_count">ユーザー (%1$d)</string>
|
||||||
|
<string name="manage_users_owner_badge">オーナー</string>
|
||||||
|
<string name="manage_users_remove">削除</string>
|
||||||
|
<string name="manage_users_or">または</string>
|
||||||
|
<string name="contractors_share">業者を共有</string>
|
||||||
|
<string name="contractors_share_upgrade_title">Pro機能</string>
|
||||||
|
<string name="contractors_share_upgrade_message">業者の共有はPro機能です。アップグレードして信頼できる業者を友人や家族と共有しましょう。</string>
|
||||||
|
<string name="contractors_import_title">業者をインポート</string>
|
||||||
|
<string name="contractors_import_message">この業者をインポートしますか?</string>
|
||||||
|
<string name="contractors_import_success">業者をインポートしました</string>
|
||||||
|
<string name="contractors_import_success_message">%1$s を連絡先に追加しました。</string>
|
||||||
|
<string name="contractors_import_failed">インポートに失敗しました</string>
|
||||||
|
<string name="contractors_shared_by">共有者: %1$s</string>
|
||||||
|
<string name="contractors_form_add_title">業者を追加</string>
|
||||||
|
<string name="contractors_form_edit_title">業者を編集</string>
|
||||||
|
<string name="contractors_form_basic_info">基本情報</string>
|
||||||
|
<string name="contractors_form_name_required">名前 *</string>
|
||||||
|
<string name="contractors_form_company">会社</string>
|
||||||
|
<string name="contractors_form_residence_optional">住居(任意)</string>
|
||||||
|
<string name="contractors_form_personal_no_residence">個人(住居なし)</string>
|
||||||
|
<string name="contractors_form_personal_visibility">この業者はあなただけが見られます</string>
|
||||||
|
<string name="contractors_form_shared_visibility">%1$s のすべてのユーザーがこの業者を見られます</string>
|
||||||
|
<string name="contractors_form_contact_info">連絡先情報</string>
|
||||||
|
<string name="contractors_form_phone">電話</string>
|
||||||
|
<string name="contractors_form_email">メール</string>
|
||||||
|
<string name="contractors_form_website">ウェブサイト</string>
|
||||||
|
<string name="contractors_form_specialties">専門分野</string>
|
||||||
|
<string name="contractors_form_address_section">住所</string>
|
||||||
|
<string name="contractors_form_street_address">番地</string>
|
||||||
|
<string name="contractors_form_city">市区町村</string>
|
||||||
|
<string name="contractors_form_state">都道府県</string>
|
||||||
|
<string name="contractors_form_zip_code">郵便番号</string>
|
||||||
|
<string name="contractors_form_notes_section">メモ</string>
|
||||||
|
<string name="contractors_form_private_notes">非公開メモ</string>
|
||||||
|
<string name="contractors_form_mark_favorite">お気に入りに登録</string>
|
||||||
|
<string name="contractors_form_add_button">追加</string>
|
||||||
|
<string name="contractors_form_save_button">保存</string>
|
||||||
|
<string name="documents_form_edit_warranty">保証を編集</string>
|
||||||
|
<string name="documents_form_edit_document">書類を編集</string>
|
||||||
|
<string name="documents_form_add_warranty">保証を追加</string>
|
||||||
|
<string name="documents_form_add_document">書類を追加</string>
|
||||||
|
<string name="documents_form_select_residence">住居を選択</string>
|
||||||
|
<string name="documents_form_residence_required">住居 *</string>
|
||||||
|
<string name="documents_form_document_type_required">書類の種類 *</string>
|
||||||
|
<string name="documents_form_title_required">タイトル *</string>
|
||||||
|
<string name="documents_form_item_name_required">品目名 *</string>
|
||||||
|
<string name="documents_form_model_number">型番</string>
|
||||||
|
<string name="documents_form_serial_number">シリアル番号</string>
|
||||||
|
<string name="documents_form_provider_required">提供元/会社 *</string>
|
||||||
|
<string name="documents_form_provider_contact">提供元の連絡先</string>
|
||||||
|
<string name="documents_form_claim_phone">請求用電話</string>
|
||||||
|
<string name="documents_form_claim_email">請求用メール</string>
|
||||||
|
<string name="documents_form_claim_website">請求用ウェブサイト</string>
|
||||||
|
<string name="documents_form_purchase_date">購入日 (YYYY-MM-DD)</string>
|
||||||
|
<string name="documents_form_warranty_start">保証開始日 (YYYY-MM-DD)</string>
|
||||||
|
<string name="documents_form_warranty_end_required">保証終了日 (YYYY-MM-DD) *</string>
|
||||||
|
<string name="documents_form_description">説明</string>
|
||||||
|
<string name="documents_form_category">カテゴリ</string>
|
||||||
|
<string name="documents_form_select_category">カテゴリを選択</string>
|
||||||
|
<string name="documents_form_category_none">なし</string>
|
||||||
|
<string name="documents_form_tags">タグ</string>
|
||||||
|
<string name="documents_form_tags_placeholder">タグ1, タグ2, タグ3</string>
|
||||||
|
<string name="documents_form_notes">メモ</string>
|
||||||
|
<string name="documents_form_active">有効</string>
|
||||||
|
<string name="documents_form_existing_photos">既存の写真 (%1$d)</string>
|
||||||
|
<string name="documents_form_new_photos">新しい写真 (%1$d/%2$d)</string>
|
||||||
|
<string name="documents_form_photos">写真 (%1$d/%2$d)</string>
|
||||||
|
<string name="documents_form_camera">カメラ</string>
|
||||||
|
<string name="documents_form_gallery">ギャラリー</string>
|
||||||
|
<string name="documents_form_image_number">画像 %1$d</string>
|
||||||
|
<string name="documents_form_remove_image">画像を削除</string>
|
||||||
|
<string name="documents_form_update_warranty">保証を更新</string>
|
||||||
|
<string name="documents_form_update_document">書類を更新</string>
|
||||||
|
<string name="documents_form_select_residence_error">住居を選択してください</string>
|
||||||
|
<string name="documents_form_title_error">タイトルは必須です</string>
|
||||||
|
<string name="documents_form_item_name_error">保証には品目名が必要です</string>
|
||||||
|
<string name="documents_form_provider_error">保証には提供元が必要です</string>
|
||||||
|
<string name="documents_form_date_placeholder">2024-01-15</string>
|
||||||
|
<string name="documents_form_date_placeholder_end">2025-01-15</string>
|
||||||
|
<string name="documents_form_failed_to_load_residences">住居の読み込みに失敗しました: %1$s</string>
|
||||||
|
<string name="profile_support">サポート</string>
|
||||||
|
<string name="profile_contact_support">サポートに問い合わせ</string>
|
||||||
|
<string name="profile_contact_support_subtitle">アカウントに関するヘルプ</string>
|
||||||
|
<string name="profile_upgrade_benefits_title">プレミアム機能を解放</string>
|
||||||
|
<string name="profile_upgrade_benefits_subtitle">Proにアップグレードして全機能を体験</string>
|
||||||
|
<string name="profile_benefit_unlimited_properties">物件数無制限</string>
|
||||||
|
<string name="profile_benefit_document_vault">書類・保証の保管</string>
|
||||||
|
<string name="profile_benefit_residence_sharing">住居の共有</string>
|
||||||
|
<string name="profile_benefit_contractor_sharing">業者の共有</string>
|
||||||
|
<string name="profile_benefit_actionable_notifications">操作可能な通知</string>
|
||||||
|
<string name="profile_benefit_widgets">ホーム画面ウィジェット</string>
|
||||||
|
<string name="profile_privacy">プライバシーポリシー</string>
|
||||||
|
<string name="profile_privacy_subtitle">プライバシーポリシーを表示</string>
|
||||||
|
<string name="profile_app_version">バージョン %1$s</string>
|
||||||
|
<string name="profile_app_name">honeyDue</string>
|
||||||
|
<string name="profile_edit_profile">プロフィールを編集</string>
|
||||||
|
<string name="delete_account_title">アカウントを削除</string>
|
||||||
|
<string name="delete_account_subtitle">アカウントを完全に削除</string>
|
||||||
|
<string name="delete_account_warning">この操作は取り消せません。すべてのデータが削除されます。</string>
|
||||||
|
<string name="delete_account_shared_warning">他のユーザーと共有しているあなた所有の住居もすべて削除されます。</string>
|
||||||
|
<string name="delete_account_confirm_password">確認のためパスワードを入力</string>
|
||||||
|
<string name="delete_account_confirm_type">確認のため DELETE と入力</string>
|
||||||
|
<string name="delete_account_button">アカウントを削除</string>
|
||||||
|
<string name="delete_account_cancel">キャンセル</string>
|
||||||
|
<string name="delete_account_success">アカウントを削除しました</string>
|
||||||
|
<string name="delete_account_failed">アカウントの削除に失敗しました</string>
|
||||||
|
<string name="notifications_daily_digest">デイリーサマリー</string>
|
||||||
|
<string name="notifications_daily_digest_desc">本日の期限・期限切れタスクの概要</string>
|
||||||
|
<string name="notifications_email_section">メール通知</string>
|
||||||
|
<string name="notifications_email_task_completed">タスク完了メール</string>
|
||||||
|
<string name="notifications_email_task_completed_desc">タスク完了時にメールを受信</string>
|
||||||
|
<string name="notifications_set_custom_time">時刻を指定</string>
|
||||||
|
<string name="notifications_change_time">変更</string>
|
||||||
|
<string name="notifications_select_time">通知時刻を選択</string>
|
||||||
|
<string name="notifications_master_title">すべての通知</string>
|
||||||
|
<string name="notifications_master_desc">すべてのカテゴリをワンタップでオン/オフ</string>
|
||||||
|
<string name="notifications_categories_section">カテゴリ</string>
|
||||||
|
<string name="notifications_category_task_reminder">タスクのリマインダー</string>
|
||||||
|
<string name="notifications_category_task_reminder_desc">近日中・まもなく期限のリマインダー</string>
|
||||||
|
<string name="notifications_category_task_overdue">期限切れタスク</string>
|
||||||
|
<string name="notifications_category_task_overdue_desc">タスクが期限を過ぎた際の通知</string>
|
||||||
|
<string name="notifications_category_residence_invite">住居への招待</string>
|
||||||
|
<string name="notifications_category_residence_invite_desc">共有住居への参加招待</string>
|
||||||
|
<string name="notifications_category_subscription">サブスクの更新</string>
|
||||||
|
<string name="notifications_category_subscription_desc">請求やプラン状況の変更</string>
|
||||||
|
<string name="notifications_open_system_settings">システム設定を開く</string>
|
||||||
|
<string name="notifications_system_settings_desc">Androidの設定でサウンド・バッジ・サイレントの動作を細かく調整</string>
|
||||||
|
<string name="common_share">共有</string>
|
||||||
|
<string name="common_import">インポート</string>
|
||||||
|
<string name="common_importing">インポート中...</string>
|
||||||
|
<string name="common_try_again">再試行</string>
|
||||||
|
<string name="home_overdue">期限切れ</string>
|
||||||
|
<string name="home_due_this_week">今週が期限</string>
|
||||||
|
<string name="home_next_30_days">今後30日間</string>
|
||||||
|
<string name="home_your_properties">あなたの物件</string>
|
||||||
|
<string name="onboarding_welcome_title">honeyDue へようこそ</string>
|
||||||
|
<string name="onboarding_welcome_subtitle">住まいのメンテナンスのパートナー</string>
|
||||||
|
<string name="onboarding_start_fresh">新しく始める</string>
|
||||||
|
<string name="onboarding_join_existing">既存の住まいに参加</string>
|
||||||
|
<string name="onboarding_already_have_account">アカウントをお持ちですか? サインイン</string>
|
||||||
|
<string name="onboarding_skip">スキップ</string>
|
||||||
|
<string name="onboarding_continue">続ける</string>
|
||||||
|
<string name="onboarding_get_started">始める</string>
|
||||||
|
<string name="onboarding_feature_tasks_title">タスクを忘れない</string>
|
||||||
|
<string name="onboarding_feature_tasks_desc">住まいのメンテナンスタスクをスマートなリマインダーで一元管理</string>
|
||||||
|
<string name="onboarding_feature_docs_title">書類をすぐに手元に</string>
|
||||||
|
<string name="onboarding_feature_docs_desc">保証書・取扱説明書・領収書を安全に保管し、いつでもアクセス</string>
|
||||||
|
<string name="onboarding_feature_contractors_title">信頼できる業者</string>
|
||||||
|
<string name="onboarding_feature_contractors_desc">業者の連絡先を整理して簡単にアクセス</string>
|
||||||
|
<string name="onboarding_feature_family_title">家族と共有</string>
|
||||||
|
<string name="onboarding_feature_family_desc">家族を招待して住まいのメンテナンスを一緒に管理</string>
|
||||||
|
<string name="onboarding_feature_notifications_title">スマート通知</string>
|
||||||
|
<string name="onboarding_feature_notifications_desc">通知から直接タスクを完了できる操作可能なリマインダー</string>
|
||||||
|
<string name="onboarding_feature_widgets_title">ホーム画面ウィジェット</string>
|
||||||
|
<string name="onboarding_feature_widgets_desc">ホーム画面からタスクとリマインダーに素早くアクセス</string>
|
||||||
|
<string name="onboarding_location_title">お住まいの場所は?</string>
|
||||||
|
<string name="onboarding_location_subtitle">お住まいの地域の気候に合ったメンテナンスタスクをご提案します</string>
|
||||||
|
<string name="onboarding_location_use_my_location">現在地を使用</string>
|
||||||
|
<string name="onboarding_location_detecting">検出中...</string>
|
||||||
|
<string name="onboarding_location_enter_zip">代わりに郵便番号を入力</string>
|
||||||
|
<string name="onboarding_location_enter_zip_prompt">郵便番号を入力</string>
|
||||||
|
<string name="onboarding_location_zip_placeholder">郵便番号</string>
|
||||||
|
<string name="onboarding_name_residence_title">住まいに名前を付ける</string>
|
||||||
|
<string name="onboarding_name_residence_subtitle">物件を識別しやすいように名前を付けましょう</string>
|
||||||
|
<string name="onboarding_name_residence_placeholder">例: 自宅、別荘、アパート</string>
|
||||||
|
<string name="onboarding_name_residence_hint">詳細は後から追加できます</string>
|
||||||
|
<string name="onboarding_create_account_title">住まいを保存</string>
|
||||||
|
<string name="onboarding_create_account_subtitle">アカウントを作成してデバイス間で同期</string>
|
||||||
|
<string name="onboarding_create_with_email">メールでアカウント作成</string>
|
||||||
|
<string name="onboarding_verify_email_title">メールを確認</string>
|
||||||
|
<string name="onboarding_verify_email_subtitle">6桁のコードをメールで送信しました。下に入力してアカウントを確認してください。</string>
|
||||||
|
<string name="onboarding_verify_email_hint">コードが届かない場合は迷惑メールフォルダをご確認ください</string>
|
||||||
|
<string name="onboarding_join_title">住居に参加</string>
|
||||||
|
<string name="onboarding_join_subtitle">共有された6文字のコードを入力して既存の住まいに参加</string>
|
||||||
|
<string name="onboarding_join_placeholder">共有コードを入力</string>
|
||||||
|
<string name="onboarding_join_button">住居に参加</string>
|
||||||
|
<string name="onboarding_tasks_title">準備完了!</string>
|
||||||
|
<string name="onboarding_tasks_subtitle">まずはいくつかのタスクから始めましょう。選ぶほど、リマインドをお手伝いします!</string>
|
||||||
|
<string name="onboarding_tasks_selected">%1$d/%2$d 件のタスクを選択中</string>
|
||||||
|
<string name="onboarding_tasks_add_popular">人気のものを追加</string>
|
||||||
|
<string name="onboarding_tasks_continue">%1$d 件のタスクを追加して続ける</string>
|
||||||
|
<string name="onboarding_tasks_skip">今はスキップ</string>
|
||||||
|
<string name="onboarding_category_hvac">空調・気候</string>
|
||||||
|
<string name="onboarding_category_safety">安全・防犯</string>
|
||||||
|
<string name="onboarding_category_plumbing">配管</string>
|
||||||
|
<string name="onboarding_category_outdoor">屋外・芝生</string>
|
||||||
|
<string name="onboarding_category_appliances">家電</string>
|
||||||
|
<string name="onboarding_category_general">住まい全般</string>
|
||||||
|
<string name="onboarding_subscription_title">Proにアップグレード</string>
|
||||||
|
<string name="onboarding_subscription_subtitle">住まいの管理をさらに次のレベルへ</string>
|
||||||
|
<string name="onboarding_subscription_pro">HONEYDUE PRO</string>
|
||||||
|
<string name="onboarding_subscription_social_proof">4.9 • 1万人以上の住宅オーナー</string>
|
||||||
|
<string name="onboarding_subscription_benefit_properties">物件数無制限</string>
|
||||||
|
<string name="onboarding_subscription_benefit_properties_desc">所有するすべての住まいを管理</string>
|
||||||
|
<string name="onboarding_subscription_benefit_reminders">スマートリマインダー</string>
|
||||||
|
<string name="onboarding_subscription_benefit_reminders_desc">メンテナンスの期限を逃さない</string>
|
||||||
|
<string name="onboarding_subscription_benefit_documents">書類保管庫</string>
|
||||||
|
<string name="onboarding_subscription_benefit_documents_desc">すべての書類を一か所に</string>
|
||||||
|
<string name="onboarding_subscription_benefit_family">ファミリー共有</string>
|
||||||
|
<string name="onboarding_subscription_benefit_family_desc">全員で情報を共有</string>
|
||||||
|
<string name="onboarding_subscription_benefit_insights">支出インサイト</string>
|
||||||
|
<string name="onboarding_subscription_benefit_insights_desc">お金の使い道を把握</string>
|
||||||
|
<string name="onboarding_subscription_benefit_contractor_sharing">業者の共有</string>
|
||||||
|
<string name="onboarding_subscription_benefit_contractor_sharing_desc">信頼できる業者を家族や友人と共有</string>
|
||||||
|
<string name="onboarding_subscription_benefit_widgets">ホーム画面ウィジェット</string>
|
||||||
|
<string name="onboarding_subscription_benefit_widgets_desc">ホーム画面から素早く操作</string>
|
||||||
|
<string name="onboarding_subscription_benefit_notifications">操作可能な通知</string>
|
||||||
|
<string name="onboarding_subscription_benefit_notifications_desc">通知から直接タスクを完了</string>
|
||||||
|
<string name="onboarding_subscription_choose_plan">プランを選択</string>
|
||||||
|
<string name="onboarding_subscription_monthly">月額</string>
|
||||||
|
<string name="onboarding_subscription_yearly">年額</string>
|
||||||
|
<string name="onboarding_subscription_save">30%お得</string>
|
||||||
|
<string name="onboarding_subscription_monthly_price">$2.99/月</string>
|
||||||
|
<string name="onboarding_subscription_yearly_price">$23.99/年</string>
|
||||||
|
<string name="onboarding_subscription_yearly_monthly">月額わずか $1.99</string>
|
||||||
|
<string name="onboarding_subscription_start_trial">7日間の無料トライアルを開始</string>
|
||||||
|
<string name="onboarding_subscription_continue_free">無料のまま続ける</string>
|
||||||
|
<string name="onboarding_subscription_trial_terms">7日間の無料トライアル、その後 %1$s。いつでも解約可能。</string>
|
||||||
|
<string name="onboarding_home_profile_title">あなたの住まいについて教えてください</string>
|
||||||
|
<string name="onboarding_home_profile_subtitle">すべて任意 — メンテナンスプランのパーソナライズに役立ちます</string>
|
||||||
|
<string name="onboarding_home_profile_systems">設備</string>
|
||||||
|
<string name="onboarding_home_profile_features">特徴</string>
|
||||||
|
<string name="onboarding_home_profile_exterior">外装</string>
|
||||||
|
<string name="onboarding_home_profile_interior">内装</string>
|
||||||
|
<string name="for_you_tab">おすすめ</string>
|
||||||
|
<string name="browse_tab">見る</string>
|
||||||
|
<string name="biometric_lock_title">アプリがロックされています</string>
|
||||||
|
<string name="biometric_lock_description">honeyDue のロックを解除するには認証してください</string>
|
||||||
|
<string name="biometric_lock_setting_title">生体認証ロック</string>
|
||||||
|
<string name="biometric_lock_setting_subtitle">アプリを開く際に認証を必須にする</string>
|
||||||
|
<string name="biometric_prompt_title">honeyDue のロックを解除</string>
|
||||||
|
<string name="biometric_prompt_subtitle">続けるには本人確認をしてください</string>
|
||||||
|
<string name="biometric_unlock_button">生体認証で解除</string>
|
||||||
|
<string name="biometric_auth_failed">認証に失敗しました</string>
|
||||||
|
<string name="biometric_not_available">このデバイスでは生体認証を利用できません</string>
|
||||||
|
<string name="notif_channel_task_reminder_name">タスクのリマインダー</string>
|
||||||
|
<string name="notif_channel_task_reminder_description">近日中・まもなく期限のタスクのリマインダー</string>
|
||||||
|
<string name="notif_channel_task_overdue_name">期限切れタスク</string>
|
||||||
|
<string name="notif_channel_task_overdue_description">タスクが期限を過ぎた際の通知</string>
|
||||||
|
<string name="notif_channel_residence_invite_name">住居への招待</string>
|
||||||
|
<string name="notif_channel_residence_invite_description">共有住居への参加招待</string>
|
||||||
|
<string name="notif_channel_subscription_name">サブスクの更新</string>
|
||||||
|
<string name="notif_channel_subscription_description">サブスクの状況と請求の更新</string>
|
||||||
|
<string name="common_selected">選択済み</string>
|
||||||
|
<string name="common_open">開く</string>
|
||||||
|
<string name="common_skip">スキップ</string>
|
||||||
|
<string name="common_or">または</string>
|
||||||
|
<string name="common_info">情報</string>
|
||||||
|
<string name="common_verified">確認済み</string>
|
||||||
|
<string name="common_warning">警告</string>
|
||||||
|
<string name="common_photo">写真</string>
|
||||||
|
<string name="common_included">含む</string>
|
||||||
|
<string name="common_verifying">確認中…</string>
|
||||||
|
<string name="time_am">午前</string>
|
||||||
|
<string name="time_pm">午後</string>
|
||||||
|
<string name="time_eve">夜</string>
|
||||||
|
<string name="error_network_title">ネットワークエラー</string>
|
||||||
|
<string name="image_failed_to_load">読み込みに失敗しました</string>
|
||||||
|
<string name="theme_appearance_title">外観</string>
|
||||||
|
<string name="theme_use_system_colors">システムカラーを使用</string>
|
||||||
|
<string name="theme_material_you_desc">Android 12以降のMaterial Youに従う(壁紙の色)</string>
|
||||||
|
<string name="paywall_choose_plan_title">プランを選択</string>
|
||||||
|
<string name="paywall_choose_plan_subtitle">Proにアップグレードして無制限にアクセス</string>
|
||||||
|
<string name="paywall_upgrade_to_pro">Proにアップグレード</string>
|
||||||
|
<string name="paywall_col_feature">機能</string>
|
||||||
|
<string name="paywall_col_free">無料</string>
|
||||||
|
<string name="paywall_col_pro">Pro</string>
|
||||||
|
<string name="paywall_feat_properties">物件</string>
|
||||||
|
<string name="paywall_feat_tasks">タスク</string>
|
||||||
|
<string name="paywall_feat_contractors">業者</string>
|
||||||
|
<string name="paywall_feat_documents">ドキュメント</string>
|
||||||
|
<string name="paywall_val_1_property">1件の物件</string>
|
||||||
|
<string name="paywall_val_10_tasks">10件のタスク</string>
|
||||||
|
<string name="paywall_val_unlimited">無制限</string>
|
||||||
|
<string name="paywall_val_not_available">利用不可</string>
|
||||||
|
<string name="reset_check_email_title">メールを確認してください</string>
|
||||||
|
<string name="reset_sent_code_to">6桁のコードを次の宛先に送信しました</string>
|
||||||
|
<string name="reset_code_expires">コードは15分で期限切れになります</string>
|
||||||
|
<string name="reset_enter_code_hint">メールに記載された6桁のコードを入力してください</string>
|
||||||
|
<string name="reset_code_verified_msg">コードを確認しました。新しいパスワードを設定してください</string>
|
||||||
|
<string name="reset_didnt_receive">コードが届きませんでしたか?</string>
|
||||||
|
<string name="reset_check_spam">見つからない場合は迷惑メールフォルダを確認してください</string>
|
||||||
|
<string name="reset_verify_failed_title">コードの確認に失敗しました</string>
|
||||||
|
<string name="verify_email_required_msg">メール認証が必要です。受信トレイで6桁のコードを確認してください。</string>
|
||||||
|
<string name="verify_email_invalid_code">有効な6桁のコードを入力してください</string>
|
||||||
|
<string name="verify_email_didnt_receive">コードが届きませんでしたか?迷惑メールフォルダを確認するか、サポートにお問い合わせください。</string>
|
||||||
|
<string name="verify_email_failed_title">認証に失敗しました</string>
|
||||||
|
<string name="reset_pw_success_msg">パスワードが正常にリセットされました</string>
|
||||||
|
<string name="reset_pw_can_login">新しいパスワードでログインできるようになりました</string>
|
||||||
|
<string name="reset_return_to_login">ログインに戻る</string>
|
||||||
|
<string name="reset_set_new_pw_title">新しいパスワードを設定</string>
|
||||||
|
<string name="reset_create_strong_pw">アカウントを保護するために強力なパスワードを作成してください</string>
|
||||||
|
<string name="reset_pw_requirements">パスワードの要件</string>
|
||||||
|
<string name="reset_pw_failed_title">パスワードのリセットに失敗しました</string>
|
||||||
|
<string name="forgot_send_code_hint">6桁の確認コードをこのアドレスに送信します</string>
|
||||||
|
<string name="forgot_check_email_msg">6桁の確認コードがメールに届いているか確認してください</string>
|
||||||
|
<string name="forgot_back_to_login">パスワードを思い出しましたか?ログインに戻る</string>
|
||||||
|
<string name="forgot_send_failed_title">リセットコードの送信に失敗しました</string>
|
||||||
|
<string name="join_property_title">物件に参加</string>
|
||||||
|
<string name="join_shared_property_header">共有物件に参加</string>
|
||||||
|
<string name="join_enter_code_desc">オーナーから提供された6文字の共有コードを入力してください。</string>
|
||||||
|
<string name="join_share_code_label">共有コード</string>
|
||||||
|
<string name="join_share_code_placeholder">ABC123</string>
|
||||||
|
<string name="join_code_helper">コードは6文字の大文字です</string>
|
||||||
|
<string name="join_joining">参加中…</string>
|
||||||
|
<string name="biometric_enter_pin">PINを入力してロックを解除</string>
|
||||||
|
<string name="biometric_pin_label">4桁のPIN</string>
|
||||||
|
<string name="biometric_incorrect_pin">PINが正しくありません</string>
|
||||||
|
<string name="biometric_unlock">ロック解除</string>
|
||||||
|
<string name="tasks_create_failed_title">タスクの作成に失敗しました</string>
|
||||||
|
<string name="tasks_all_title">すべてのタスク</string>
|
||||||
|
<string name="tasks_add">タスクを追加</string>
|
||||||
|
<string name="tasks_load_failed_title">タスクの読み込みに失敗しました</string>
|
||||||
|
<string name="tasks_all_empty_subtitle">最初のタスクを作成して始めましょう</string>
|
||||||
|
<string name="tasks_add_property_first">まず「住居」タブから物件を追加してください</string>
|
||||||
|
<string name="residences_upgrade_to_add">アップグレードして追加</string>
|
||||||
|
<string name="residences_primary_cd">主な住居</string>
|
||||||
|
<string name="residence_unit_label">ユニット: %1$s</string>
|
||||||
|
<string name="residence_error_loading_tasks">タスクの読み込みエラー: %1$s</string>
|
||||||
|
<string name="residence_error_loading_contractors">業者の読み込みエラー: %1$s</string>
|
||||||
|
<string name="profile_subscription_cd">サブスクリプション</string>
|
||||||
|
<string name="onboarding_sub_cancel_note">設定からいつでもキャンセル可能 • 契約の縛りなし</string>
|
||||||
|
<string name="onboarding_joining_residence">住居に参加中…</string>
|
||||||
|
<string name="onboarding_sub_trial_legal">7日間の無料トライアル、その後 %1$s</string>
|
||||||
|
<string name="common_collapse">折りたたむ</string>
|
||||||
|
<string name="common_expand">展開</string>
|
||||||
|
<string name="common_not_selected">未選択</string>
|
||||||
|
<string name="auth_logging_in">ログイン中…</string>
|
||||||
|
<string name="auth_requirement_met">要件を満たしています</string>
|
||||||
|
<string name="auth_requirement_not_met">要件を満たしていません</string>
|
||||||
|
<string name="error_something_wrong">問題が発生しました</string>
|
||||||
|
<string name="paywall_feat_not_included">含まれていません</string>
|
||||||
|
<string name="completions_contractor_prefix">業者:</string>
|
||||||
|
<string name="completions_completed_by_prefix">完了者:</string>
|
||||||
|
<string name="completions_remove_photo">写真を削除</string>
|
||||||
|
<string name="completions_star_rating_cd">星%1$d個</string>
|
||||||
|
<string name="photos_completion_title">完了写真</string>
|
||||||
|
<string name="photos_task_completion_cd">タスク完了の写真</string>
|
||||||
|
<string name="documents_residence_ref">住居 #%1$d</string>
|
||||||
|
<string name="documents_task_ref">タスク #%1$d</string>
|
||||||
|
<string name="documents_image_index">画像 %1$d / %2$d</string>
|
||||||
|
<string name="documents_images_title">ドキュメント画像</string>
|
||||||
|
<string name="documents_empty_no_warranties">保証が見つかりません</string>
|
||||||
|
<string name="documents_empty_no_documents">ドキュメントが見つかりません</string>
|
||||||
|
<string name="documents_days_remaining_count">残り%1$d日</string>
|
||||||
|
<string name="contractors_property_ref">物件 #</string>
|
||||||
|
<string name="residence_share_failed">住居の共有に失敗しました</string>
|
||||||
|
<string name="theme_default">デフォルト</string>
|
||||||
|
<string name="theme_default_desc">鮮やかなiOSシステムカラー</string>
|
||||||
|
<string name="theme_teal">ティール</string>
|
||||||
|
<string name="theme_teal_desc">温かみのあるアクセントの青緑</string>
|
||||||
|
<string name="theme_ocean">オーシャン</string>
|
||||||
|
<string name="theme_ocean_desc">深い青とコーラルの色合い</string>
|
||||||
|
<string name="theme_forest">フォレスト</string>
|
||||||
|
<string name="theme_forest_desc">アースグリーンと黄金色の色合い</string>
|
||||||
|
<string name="theme_sunset">サンセット</string>
|
||||||
|
<string name="theme_sunset_desc">温かいオレンジと赤</string>
|
||||||
|
<string name="theme_monochrome">モノクロ</string>
|
||||||
|
<string name="theme_monochrome_desc">上品なグレースケール</string>
|
||||||
|
<string name="theme_lavender">ラベンダー</string>
|
||||||
|
<string name="theme_lavender_desc">ピンクのアクセントが効いた柔らかな紫</string>
|
||||||
|
<string name="theme_crimson">クリムゾン</string>
|
||||||
|
<string name="theme_crimson_desc">温かいハイライトのある鮮やかな赤</string>
|
||||||
|
<string name="theme_midnight">ミッドナイト</string>
|
||||||
|
<string name="theme_midnight_desc">スカイブルーを伴う深いネイビー</string>
|
||||||
|
<string name="theme_desert">デザート</string>
|
||||||
|
<string name="theme_desert_desc">温かいテラコッタと砂の色合い</string>
|
||||||
|
<string name="theme_mint">ミント</string>
|
||||||
|
<string name="theme_mint_desc">ターコイズを伴う爽やかなグリーン</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -472,4 +472,585 @@
|
|||||||
<string name="subscription_features">프리미엄 기능</string>
|
<string name="subscription_features">프리미엄 기능</string>
|
||||||
<string name="subscription_limit_properties">플랜의 부동산 제한에 도달했습니다</string>
|
<string name="subscription_limit_properties">플랜의 부동산 제한에 도달했습니다</string>
|
||||||
<string name="subscription_limit_tasks">플랜의 작업 제한에 도달했습니다</string>
|
<string name="subscription_limit_tasks">플랜의 작업 제한에 도달했습니다</string>
|
||||||
|
<string name="home_profile_heating">난방</string>
|
||||||
|
<string name="home_profile_cooling">냉방</string>
|
||||||
|
<string name="home_profile_water_heater">온수기</string>
|
||||||
|
<string name="home_profile_pool">수영장</string>
|
||||||
|
<string name="home_profile_sprinkler_system">스프링클러 시스템</string>
|
||||||
|
<string name="home_profile_fireplace">벽난로</string>
|
||||||
|
<string name="home_profile_garage">차고</string>
|
||||||
|
<string name="home_profile_basement">지하실</string>
|
||||||
|
<string name="home_profile_attic">다락방</string>
|
||||||
|
<string name="home_profile_septic">정화조</string>
|
||||||
|
<string name="home_profile_roof_type">지붕 유형</string>
|
||||||
|
<string name="home_profile_exterior">외장</string>
|
||||||
|
<string name="home_profile_flooring">바닥재</string>
|
||||||
|
<string name="home_profile_landscaping">조경</string>
|
||||||
|
<string name="onboarding_first_task_selected_count">작업 %1$d개 선택됨</string>
|
||||||
|
<string name="onboarding_first_task_add_continue">작업 %1$d개 추가 후 계속</string>
|
||||||
|
<string name="onboarding_first_task_finding">집에 맞는 작업을 찾는 중...</string>
|
||||||
|
<string name="onboarding_first_task_no_suggestions">아직 맞춤 추천이 없습니다 — 전체 카탈로그를 둘러보거나 이 단계를 건너뛰세요.</string>
|
||||||
|
<string name="onboarding_first_task_browse_all">전체 보기</string>
|
||||||
|
<string name="onboarding_first_task_skip">건너뛰기</string>
|
||||||
|
<string name="onboarding_first_task_suggestions_error">추천을 불러올 수 없습니다</string>
|
||||||
|
<string name="onboarding_first_task_connection_error">연결을 확인하고 다시 시도하세요.</string>
|
||||||
|
<string name="onboarding_first_task_loading_catalog">작업 카탈로그를 불러오는 중...</string>
|
||||||
|
<string name="onboarding_first_task_catalog_error">작업 카탈로그를 불러올 수 없습니다</string>
|
||||||
|
<string name="onboarding_first_task_no_templates">지금 사용할 수 있는 템플릿이 없습니다.</string>
|
||||||
|
<string name="onboarding_first_task_selected">선택됨</string>
|
||||||
|
<string name="onboarding_first_task_offline">오프라인</string>
|
||||||
|
<string name="onboarding_first_task_skip_for_now">나중에</string>
|
||||||
|
<string name="upgrade_hero_title">Pro로 업그레이드</string>
|
||||||
|
<string name="upgrade_hero_subtitle">honeyDue의 모든 기능을 활용하세요</string>
|
||||||
|
<string name="upgrade_choose_plan">플랜 선택</string>
|
||||||
|
<string name="upgrade_plan_yearly">연간</string>
|
||||||
|
<string name="upgrade_plan_monthly">월간</string>
|
||||||
|
<string name="upgrade_plan_save_50">50% 절약</string>
|
||||||
|
<string name="upgrade_best_value">최고의 혜택</string>
|
||||||
|
<string name="upgrade_whats_included">포함 사항</string>
|
||||||
|
<string name="upgrade_feature_unlimited_properties">무제한 자산</string>
|
||||||
|
<string name="upgrade_feature_unlimited_properties_desc">모든 집의 유지보수를 관리하세요</string>
|
||||||
|
<string name="upgrade_feature_unlimited_tasks">무제한 작업</string>
|
||||||
|
<string name="upgrade_feature_unlimited_tasks_desc">유지보수 작업을 다시는 잊지 마세요</string>
|
||||||
|
<string name="upgrade_feature_contractor_management">업체 관리</string>
|
||||||
|
<string name="upgrade_feature_contractor_management_desc">신뢰하는 업체를 저장하고 평가하세요</string>
|
||||||
|
<string name="upgrade_feature_document_vault">문서 보관함</string>
|
||||||
|
<string name="upgrade_feature_document_vault_desc">보증서, 영수증, 설명서를 보관하세요</string>
|
||||||
|
<string name="upgrade_feature_family_sharing">가족 공유</string>
|
||||||
|
<string name="upgrade_feature_family_sharing_desc">가족을 초대해 함께 관리하세요</string>
|
||||||
|
<string name="upgrade_feature_smart_reminders">스마트 알림</string>
|
||||||
|
<string name="upgrade_feature_smart_reminders_desc">작업 기한이 되면 알림을 받으세요</string>
|
||||||
|
<string name="upgrade_feature_document_warranty_storage">문서 및 보증서 보관</string>
|
||||||
|
<string name="upgrade_subscribe_now">지금 구독하기</string>
|
||||||
|
<string name="upgrade_restore_purchases">구매 복원</string>
|
||||||
|
<string name="upgrade_terms_text">구독은 현재 기간 종료 최소 24시간 전에 취소하지 않으면 자동으로 갱신됩니다. 기기 설정에서 구독을 관리하세요.</string>
|
||||||
|
<string name="upgrade_terms_of_use">이용약관</string>
|
||||||
|
<string name="upgrade_included">포함됨</string>
|
||||||
|
<string name="upgrade_compare_free_vs_pro">무료 vs Pro 비교</string>
|
||||||
|
<string name="upgrade_maybe_later">나중에</string>
|
||||||
|
<string name="upgrade_warning">경고</string>
|
||||||
|
<string name="upgrade_subscription_active">구독 활성화됨</string>
|
||||||
|
<string name="upgrade_subscription_active_message">이제 모든 Pro 기능을 이용할 수 있습니다!</string>
|
||||||
|
<string name="upgrade_feature_required_title">업그레이드 필요</string>
|
||||||
|
<string name="upgrade_feature_required_message">이 기능은 Pro 구독에서 사용할 수 있습니다.</string>
|
||||||
|
<string name="upgrade_prompt_default_message">모든 기능을 무제한으로 이용하세요</string>
|
||||||
|
<string name="upgrade_product_monthly_name">honeyDue Pro 월간</string>
|
||||||
|
<string name="upgrade_product_annual_name">honeyDue Pro 연간</string>
|
||||||
|
<string name="upgrade_billed_monthly">월간 청구</string>
|
||||||
|
<string name="upgrade_billed_annually">연간 청구</string>
|
||||||
|
<string name="upgrade_save_22">22% 절약</string>
|
||||||
|
<string name="tasks_column_done">완료</string>
|
||||||
|
<string name="tasks_column_archived">보관됨</string>
|
||||||
|
<string name="tasks_column_empty">작업 없음</string>
|
||||||
|
<string name="tasks_new_title">새 작업</string>
|
||||||
|
<string name="tasks_title_field_label">제목</string>
|
||||||
|
<string name="tasks_title_placeholder">예: 온수기 청소</string>
|
||||||
|
<string name="tasks_description_placeholder">선택 사항 세부 정보</string>
|
||||||
|
<string name="tasks_due_date_optional_label">기한 (선택)</string>
|
||||||
|
<string name="tasks_due_date_placeholder_format">yyyy-MM-dd</string>
|
||||||
|
<string name="tasks_due_date_blank_helper">기한이 없으면 비워 두세요</string>
|
||||||
|
<string name="tasks_estimated_cost_optional_label">예상 비용 (선택)</string>
|
||||||
|
<string name="suggestions_title">추천 작업</string>
|
||||||
|
<string name="suggestions_skip">건너뛰기</string>
|
||||||
|
<string name="suggestions_accept">수락</string>
|
||||||
|
<string name="suggestions_load_failed">추천을 불러올 수 없습니다</string>
|
||||||
|
<string name="suggestions_empty_title">아직 추천이 없습니다</string>
|
||||||
|
<string name="suggestions_empty_subtitle">홈 프로필을 완성하면 맞춤 추천을 볼 수 있습니다.</string>
|
||||||
|
<string name="completion_history_title">완료 내역</string>
|
||||||
|
<string name="completion_history_count_one">완료 %1$d건</string>
|
||||||
|
<string name="completion_history_count_other">완료 %1$d건</string>
|
||||||
|
<string name="completion_history_loading">완료 내역을 불러오는 중...</string>
|
||||||
|
<string name="completion_history_load_failed">완료 내역을 불러오지 못했습니다</string>
|
||||||
|
<string name="completion_history_empty_title">아직 완료 내역이 없습니다</string>
|
||||||
|
<string name="completion_history_empty_message">이 작업은 아직 완료되지 않았습니다.</string>
|
||||||
|
<string name="completion_history_completed_by">%1$s님이 완료함</string>
|
||||||
|
<string name="completion_history_view_photo">사진 보기</string>
|
||||||
|
<string name="manage_users_remove_user">사용자 삭제</string>
|
||||||
|
<string name="manage_users_copy_code">코드 복사</string>
|
||||||
|
<string name="manage_users_code_copied">코드가 클립보드에 복사되었습니다</string>
|
||||||
|
<string name="manage_users_load_failed">사용자를 불러올 수 없습니다</string>
|
||||||
|
<string name="manage_users_remove_confirm">이 자산에서 %1$s님을 삭제하시겠습니까?</string>
|
||||||
|
<string name="properties_shared_users_count">공유 사용자 (%1$d)</string>
|
||||||
|
<string name="properties_no_shared_users">공유 사용자 없음</string>
|
||||||
|
<string name="properties_shared_users_helper">이 주거지에 접근 권한이 있는 사용자입니다. 공유 버튼으로 다른 사람을 초대하세요.</string>
|
||||||
|
<string name="properties_remove_user_confirm">이 주거지에서 %1$s님을 삭제하시겠습니까?</string>
|
||||||
|
<string name="properties_remove_button">삭제</string>
|
||||||
|
<string name="documents_expires_label">만료일</string>
|
||||||
|
<string name="auth_password_requirement_length">8자 이상</string>
|
||||||
|
<string name="auth_password_requirement_uppercase">대문자 포함</string>
|
||||||
|
<string name="auth_password_requirement_lowercase">소문자 포함</string>
|
||||||
|
<string name="auth_password_requirement_digit">숫자 포함</string>
|
||||||
|
<string name="auth_password_requirement_match">비밀번호 일치</string>
|
||||||
|
<string name="auth_password_requirements_title">비밀번호 요구 사항</string>
|
||||||
|
<string name="auth_password_complexity_error">비밀번호는 8자 이상이며 대문자, 소문자, 숫자를 각각 하나 이상 포함해야 합니다</string>
|
||||||
|
<string name="properties_join_residence_title">거주지 참여</string>
|
||||||
|
<string name="properties_join_residence_message">이 공유 거주지에 참여하시겠어요?</string>
|
||||||
|
<string name="properties_join_success">거주지 참여 완료</string>
|
||||||
|
<string name="properties_join_success_message">이제 %1$s에 접근할 수 있습니다.</string>
|
||||||
|
<string name="properties_join_failed">참여 실패</string>
|
||||||
|
<string name="properties_joining">참여 중...</string>
|
||||||
|
<string name="properties_shared_by">공유자: %1$s</string>
|
||||||
|
<string name="properties_expires">만료: %1$s</string>
|
||||||
|
<string name="properties_share_upgrade_title">Pro 기능</string>
|
||||||
|
<string name="properties_share_upgrade_message">거주지 공유는 Pro 기능입니다. 업그레이드하여 가족을 초대하고 집 관리를 함께하세요.</string>
|
||||||
|
<string name="tasks_failed_to_cancel">작업 취소에 실패했습니다</string>
|
||||||
|
<string name="tasks_failed_to_restore">작업 복원에 실패했습니다</string>
|
||||||
|
<string name="tasks_failed_to_mark_in_progress">작업을 진행 중으로 표시하지 못했습니다</string>
|
||||||
|
<string name="tasks_failed_to_archive">작업 보관에 실패했습니다</string>
|
||||||
|
<string name="tasks_failed_to_unarchive">작업 보관 해제에 실패했습니다</string>
|
||||||
|
<string name="tasks_card_in_progress">진행 중</string>
|
||||||
|
<string name="tasks_card_actions">작업</string>
|
||||||
|
<string name="tasks_card_mark_in_progress">진행 중으로 표시</string>
|
||||||
|
<string name="tasks_card_complete_task">작업 완료</string>
|
||||||
|
<string name="tasks_card_edit_task">작업 편집</string>
|
||||||
|
<string name="tasks_card_cancel_task">작업 취소</string>
|
||||||
|
<string name="tasks_card_restore_task">작업 복원</string>
|
||||||
|
<string name="tasks_card_archive_task">작업 보관</string>
|
||||||
|
<string name="tasks_card_unarchive_task">작업 보관 해제</string>
|
||||||
|
<string name="tasks_card_not_available">해당 없음</string>
|
||||||
|
<string name="tasks_card_completed_by">담당: %1$s</string>
|
||||||
|
<string name="tasks_card_cost">비용: $%1$s</string>
|
||||||
|
<string name="tasks_card_view_photos">사진 보기 (%1$d)</string>
|
||||||
|
<string name="tasks_add_new">새 작업 추가</string>
|
||||||
|
<string name="tasks_property_required">자산 *</string>
|
||||||
|
<string name="tasks_property_error">자산을 입력하세요</string>
|
||||||
|
<string name="tasks_browse_templates">작업 템플릿 둘러보기</string>
|
||||||
|
<string name="tasks_common_tasks">공통 작업 %1$d개</string>
|
||||||
|
<string name="tasks_category_error">카테고리를 선택하세요</string>
|
||||||
|
<string name="tasks_interval_days">간격(일)</string>
|
||||||
|
<string name="tasks_interval_override">기본 반복 간격 재정의</string>
|
||||||
|
<string name="tasks_custom_interval_help">각 반복 사이의 일수</string>
|
||||||
|
<string name="tasks_due_date_format_error">마감일을 입력하세요 (형식: YYYY-MM-DD)</string>
|
||||||
|
<string name="tasks_due_date_format">형식: YYYY-MM-DD</string>
|
||||||
|
<string name="tasks_create">작업 만들기</string>
|
||||||
|
<string name="tasks_in_progress_label">진행 중</string>
|
||||||
|
<string name="templates_title">작업 템플릿</string>
|
||||||
|
<string name="templates_done">완료</string>
|
||||||
|
<string name="templates_search_placeholder">템플릿 검색...</string>
|
||||||
|
<string name="templates_clear">지우기</string>
|
||||||
|
<string name="templates_result">개 결과</string>
|
||||||
|
<string name="templates_results">개 결과</string>
|
||||||
|
<string name="templates_no_results_title">템플릿 없음</string>
|
||||||
|
<string name="templates_no_results_message">다른 검색어를 시도해 보세요</string>
|
||||||
|
<string name="templates_empty_title">사용 가능한 템플릿 없음</string>
|
||||||
|
<string name="templates_empty_message">템플릿이 로드되면 여기에 표시됩니다</string>
|
||||||
|
<string name="templates_expand">펼치기</string>
|
||||||
|
<string name="templates_collapse">접기</string>
|
||||||
|
<string name="templates_add">추가</string>
|
||||||
|
<string name="templates_all_categories">전체</string>
|
||||||
|
<string name="templates_apply">적용</string>
|
||||||
|
<string name="templates_apply_count">적용 (%1$d)</string>
|
||||||
|
<string name="templates_selected_count">%1$d개 선택됨</string>
|
||||||
|
<string name="templates_retry">다시 시도</string>
|
||||||
|
<string name="templates_load_failed">템플릿을 불러오지 못했습니다</string>
|
||||||
|
<string name="templates_create_failed">작업을 만들지 못했습니다</string>
|
||||||
|
<string name="completions_complete_task_title">작업 완료: %1$s</string>
|
||||||
|
<string name="completions_select_contractor">시공업체 선택 (선택 사항)</string>
|
||||||
|
<string name="completions_choose_contractor_placeholder">시공업체를 선택하거나 비워 두세요</string>
|
||||||
|
<string name="completions_expand">펼치기</string>
|
||||||
|
<string name="completions_none_manual">없음 (직접 입력)</string>
|
||||||
|
<string name="completions_loading_contractors">시공업체 불러오는 중...</string>
|
||||||
|
<string name="completions_error_loading_contractors">시공업체를 불러오는 중 오류 발생</string>
|
||||||
|
<string name="completions_completed_by_name">완료자 이름 (선택 사항)</string>
|
||||||
|
<string name="completions_completed_by_placeholder">위 시공업체를 사용하지 않는 경우 이름 입력</string>
|
||||||
|
<string name="completions_actual_cost_optional">실제 비용 (선택 사항)</string>
|
||||||
|
<string name="completions_notes_optional">메모 (선택 사항)</string>
|
||||||
|
<string name="completions_rating">평점: 5점 중 %1$d점</string>
|
||||||
|
<string name="completions_add_images">이미지 추가</string>
|
||||||
|
<string name="completions_take_photo">사진 촬영</string>
|
||||||
|
<string name="completions_choose_from_library">라이브러리에서 선택</string>
|
||||||
|
<string name="completions_images_selected">이미지 %1$d개 선택됨</string>
|
||||||
|
<string name="completions_remove_image">이미지 삭제</string>
|
||||||
|
<string name="completions_complete_button">완료</string>
|
||||||
|
<string name="completions_quality_rating">품질 평점</string>
|
||||||
|
<string name="completions_photos_count">사진 (%1$d/%2$d)</string>
|
||||||
|
<string name="completions_camera">카메라</string>
|
||||||
|
<string name="completions_library">라이브러리</string>
|
||||||
|
<string name="completions_add_photos_helper">완료된 작업 사진 추가 (선택 사항)</string>
|
||||||
|
<string name="completions_contractor_helper">이 작업 완료를 시공업체와 연결</string>
|
||||||
|
<string name="completions_details_section">완료 세부 정보</string>
|
||||||
|
<string name="completions_optional_info">모든 항목은 선택 사항입니다</string>
|
||||||
|
<string name="completions_notes_helper">완료된 작업에 대한 메모를 추가하세요</string>
|
||||||
|
<string name="completions_notes_placeholder">수행한 작업, 발견된 문제 등을 적어 주세요.</string>
|
||||||
|
<string name="completions_rate_quality">수행된 작업의 품질을 평가하세요</string>
|
||||||
|
<string name="completions_enter_manually">아래에 이름을 직접 입력하세요</string>
|
||||||
|
<string name="manage_users_title">사용자 관리</string>
|
||||||
|
<string name="manage_users_invite_title">다른 사용자 초대</string>
|
||||||
|
<string name="manage_users_easy_share">간편 공유</string>
|
||||||
|
<string name="manage_users_send_invite">초대 링크 보내기</string>
|
||||||
|
<string name="manage_users_easy_share_desc">메시지, 이메일 또는 공유로 .honeydue 파일을 보내세요. 받는 사람은 탭만 하면 참여됩니다.</string>
|
||||||
|
<string name="manage_users_share_code">공유 코드</string>
|
||||||
|
<string name="manage_users_no_code">활성 코드 없음</string>
|
||||||
|
<string name="manage_users_generate">코드 생성</string>
|
||||||
|
<string name="manage_users_generate_new">새 코드 생성</string>
|
||||||
|
<string name="manage_users_code_desc">이 6자리 코드를 공유하세요. 앱에서 입력하면 참여할 수 있습니다.</string>
|
||||||
|
<string name="manage_users_users_count">사용자 (%1$d)</string>
|
||||||
|
<string name="manage_users_owner_badge">소유자</string>
|
||||||
|
<string name="manage_users_remove">삭제</string>
|
||||||
|
<string name="manage_users_or">또는</string>
|
||||||
|
<string name="contractors_share">시공업체 공유</string>
|
||||||
|
<string name="contractors_share_upgrade_title">Pro 기능</string>
|
||||||
|
<string name="contractors_share_upgrade_message">시공업체 공유는 Pro 기능입니다. 업그레이드하여 신뢰하는 시공업체를 친구와 가족과 공유하세요.</string>
|
||||||
|
<string name="contractors_import_title">시공업체 가져오기</string>
|
||||||
|
<string name="contractors_import_message">이 시공업체를 가져오시겠어요?</string>
|
||||||
|
<string name="contractors_import_success">시공업체 가져오기 완료</string>
|
||||||
|
<string name="contractors_import_success_message">%1$s이(가) 연락처에 추가되었습니다.</string>
|
||||||
|
<string name="contractors_import_failed">가져오기 실패</string>
|
||||||
|
<string name="contractors_shared_by">공유자: %1$s</string>
|
||||||
|
<string name="contractors_form_add_title">시공업체 추가</string>
|
||||||
|
<string name="contractors_form_edit_title">시공업체 편집</string>
|
||||||
|
<string name="contractors_form_basic_info">기본 정보</string>
|
||||||
|
<string name="contractors_form_name_required">이름 *</string>
|
||||||
|
<string name="contractors_form_company">회사</string>
|
||||||
|
<string name="contractors_form_residence_optional">거주지 (선택 사항)</string>
|
||||||
|
<string name="contractors_form_personal_no_residence">개인 (거주지 없음)</string>
|
||||||
|
<string name="contractors_form_personal_visibility">나만 이 시공업체를 볼 수 있습니다</string>
|
||||||
|
<string name="contractors_form_shared_visibility">%1$s의 모든 사용자가 이 시공업체를 볼 수 있습니다</string>
|
||||||
|
<string name="contractors_form_contact_info">연락처 정보</string>
|
||||||
|
<string name="contractors_form_phone">전화번호</string>
|
||||||
|
<string name="contractors_form_email">이메일</string>
|
||||||
|
<string name="contractors_form_website">웹사이트</string>
|
||||||
|
<string name="contractors_form_specialties">전문 분야</string>
|
||||||
|
<string name="contractors_form_address_section">주소</string>
|
||||||
|
<string name="contractors_form_street_address">도로명 주소</string>
|
||||||
|
<string name="contractors_form_city">도시</string>
|
||||||
|
<string name="contractors_form_state">주/도</string>
|
||||||
|
<string name="contractors_form_zip_code">우편번호</string>
|
||||||
|
<string name="contractors_form_notes_section">메모</string>
|
||||||
|
<string name="contractors_form_private_notes">비공개 메모</string>
|
||||||
|
<string name="contractors_form_mark_favorite">즐겨찾기로 표시</string>
|
||||||
|
<string name="contractors_form_add_button">추가</string>
|
||||||
|
<string name="contractors_form_save_button">저장</string>
|
||||||
|
<string name="documents_form_edit_warranty">보증서 편집</string>
|
||||||
|
<string name="documents_form_edit_document">문서 편집</string>
|
||||||
|
<string name="documents_form_add_warranty">보증서 추가</string>
|
||||||
|
<string name="documents_form_add_document">문서 추가</string>
|
||||||
|
<string name="documents_form_select_residence">거주지 선택</string>
|
||||||
|
<string name="documents_form_residence_required">거주지 *</string>
|
||||||
|
<string name="documents_form_document_type_required">문서 유형 *</string>
|
||||||
|
<string name="documents_form_title_required">제목 *</string>
|
||||||
|
<string name="documents_form_item_name_required">품목명 *</string>
|
||||||
|
<string name="documents_form_model_number">모델 번호</string>
|
||||||
|
<string name="documents_form_serial_number">일련번호</string>
|
||||||
|
<string name="documents_form_provider_required">제공자/회사 *</string>
|
||||||
|
<string name="documents_form_provider_contact">제공자 연락처</string>
|
||||||
|
<string name="documents_form_claim_phone">청구 전화번호</string>
|
||||||
|
<string name="documents_form_claim_email">청구 이메일</string>
|
||||||
|
<string name="documents_form_claim_website">청구 웹사이트</string>
|
||||||
|
<string name="documents_form_purchase_date">구매일 (YYYY-MM-DD)</string>
|
||||||
|
<string name="documents_form_warranty_start">보증 시작일 (YYYY-MM-DD)</string>
|
||||||
|
<string name="documents_form_warranty_end_required">보증 종료일 (YYYY-MM-DD) *</string>
|
||||||
|
<string name="documents_form_description">설명</string>
|
||||||
|
<string name="documents_form_category">카테고리</string>
|
||||||
|
<string name="documents_form_select_category">카테고리 선택</string>
|
||||||
|
<string name="documents_form_category_none">없음</string>
|
||||||
|
<string name="documents_form_tags">태그</string>
|
||||||
|
<string name="documents_form_tags_placeholder">태그1, 태그2, 태그3</string>
|
||||||
|
<string name="documents_form_notes">메모</string>
|
||||||
|
<string name="documents_form_active">활성</string>
|
||||||
|
<string name="documents_form_existing_photos">기존 사진 (%1$d)</string>
|
||||||
|
<string name="documents_form_new_photos">새 사진 (%1$d/%2$d)</string>
|
||||||
|
<string name="documents_form_photos">사진 (%1$d/%2$d)</string>
|
||||||
|
<string name="documents_form_camera">카메라</string>
|
||||||
|
<string name="documents_form_gallery">갤러리</string>
|
||||||
|
<string name="documents_form_image_number">이미지 %1$d</string>
|
||||||
|
<string name="documents_form_remove_image">이미지 삭제</string>
|
||||||
|
<string name="documents_form_update_warranty">보증서 업데이트</string>
|
||||||
|
<string name="documents_form_update_document">문서 업데이트</string>
|
||||||
|
<string name="documents_form_select_residence_error">거주지를 선택하세요</string>
|
||||||
|
<string name="documents_form_title_error">제목을 입력하세요</string>
|
||||||
|
<string name="documents_form_item_name_error">보증서에는 품목명이 필요합니다</string>
|
||||||
|
<string name="documents_form_provider_error">보증서에는 제공자가 필요합니다</string>
|
||||||
|
<string name="documents_form_date_placeholder">2024-01-15</string>
|
||||||
|
<string name="documents_form_date_placeholder_end">2025-01-15</string>
|
||||||
|
<string name="documents_form_failed_to_load_residences">거주지를 불러오지 못했습니다: %1$s</string>
|
||||||
|
<string name="profile_support">지원</string>
|
||||||
|
<string name="profile_contact_support">고객 지원 문의</string>
|
||||||
|
<string name="profile_contact_support_subtitle">계정 관련 도움 받기</string>
|
||||||
|
<string name="profile_upgrade_benefits_title">프리미엄 기능 잠금 해제</string>
|
||||||
|
<string name="profile_upgrade_benefits_subtitle">Pro로 업그레이드하여 모든 기능을 경험하세요</string>
|
||||||
|
<string name="profile_benefit_unlimited_properties">무제한 자산</string>
|
||||||
|
<string name="profile_benefit_document_vault">문서 및 보증서 보관</string>
|
||||||
|
<string name="profile_benefit_residence_sharing">거주지 공유</string>
|
||||||
|
<string name="profile_benefit_contractor_sharing">시공업체 공유</string>
|
||||||
|
<string name="profile_benefit_actionable_notifications">실행 가능한 알림</string>
|
||||||
|
<string name="profile_benefit_widgets">홈 화면 위젯</string>
|
||||||
|
<string name="profile_privacy">개인정보 처리방침</string>
|
||||||
|
<string name="profile_privacy_subtitle">개인정보 처리방침 보기</string>
|
||||||
|
<string name="profile_app_version">버전 %1$s</string>
|
||||||
|
<string name="profile_app_name">honeyDue</string>
|
||||||
|
<string name="profile_edit_profile">프로필 편집</string>
|
||||||
|
<string name="delete_account_title">계정 삭제</string>
|
||||||
|
<string name="delete_account_subtitle">계정을 영구적으로 삭제합니다</string>
|
||||||
|
<string name="delete_account_warning">이 작업은 영구적이며 되돌릴 수 없습니다. 모든 데이터가 삭제됩니다.</string>
|
||||||
|
<string name="delete_account_shared_warning">다른 사용자와 공유 중인 소유 거주지도 함께 삭제됩니다.</string>
|
||||||
|
<string name="delete_account_confirm_password">확인을 위해 비밀번호를 입력하세요</string>
|
||||||
|
<string name="delete_account_confirm_type">확인하려면 DELETE를 입력하세요</string>
|
||||||
|
<string name="delete_account_button">내 계정 삭제</string>
|
||||||
|
<string name="delete_account_cancel">취소</string>
|
||||||
|
<string name="delete_account_success">계정이 삭제되었습니다</string>
|
||||||
|
<string name="delete_account_failed">계정 삭제에 실패했습니다</string>
|
||||||
|
<string name="notifications_daily_digest">일일 요약</string>
|
||||||
|
<string name="notifications_daily_digest_desc">오늘 마감 및 연체된 작업 요약</string>
|
||||||
|
<string name="notifications_email_section">이메일 알림</string>
|
||||||
|
<string name="notifications_email_task_completed">작업 완료 이메일</string>
|
||||||
|
<string name="notifications_email_task_completed_desc">작업이 완료되면 이메일 받기</string>
|
||||||
|
<string name="notifications_set_custom_time">사용자 지정 시간 설정</string>
|
||||||
|
<string name="notifications_change_time">변경</string>
|
||||||
|
<string name="notifications_select_time">알림 시간 선택</string>
|
||||||
|
<string name="notifications_master_title">모든 알림</string>
|
||||||
|
<string name="notifications_master_desc">모든 카테고리를 한 번에 켜거나 끄기</string>
|
||||||
|
<string name="notifications_categories_section">카테고리</string>
|
||||||
|
<string name="notifications_category_task_reminder">작업 알림</string>
|
||||||
|
<string name="notifications_category_task_reminder_desc">예정 및 임박한 작업 알림</string>
|
||||||
|
<string name="notifications_category_task_overdue">연체된 작업</string>
|
||||||
|
<string name="notifications_category_task_overdue_desc">작업 마감일이 지나면 알림</string>
|
||||||
|
<string name="notifications_category_residence_invite">거주지 초대</string>
|
||||||
|
<string name="notifications_category_residence_invite_desc">공유 거주지 참여 초대</string>
|
||||||
|
<string name="notifications_category_subscription">구독 업데이트</string>
|
||||||
|
<string name="notifications_category_subscription_desc">결제 및 요금제 상태 변경</string>
|
||||||
|
<string name="notifications_open_system_settings">시스템 설정 열기</string>
|
||||||
|
<string name="notifications_system_settings_desc">Android 설정에서 소리, 배지, 방해 금지 모드를 세부 조정하세요</string>
|
||||||
|
<string name="common_share">공유</string>
|
||||||
|
<string name="common_import">가져오기</string>
|
||||||
|
<string name="common_importing">가져오는 중...</string>
|
||||||
|
<string name="common_try_again">다시 시도</string>
|
||||||
|
<string name="home_overdue">연체</string>
|
||||||
|
<string name="home_due_this_week">이번 주 마감</string>
|
||||||
|
<string name="home_next_30_days">다음 30일</string>
|
||||||
|
<string name="home_your_properties">내 자산</string>
|
||||||
|
<string name="onboarding_welcome_title">honeyDue에 오신 것을 환영합니다</string>
|
||||||
|
<string name="onboarding_welcome_subtitle">집 관리 도우미</string>
|
||||||
|
<string name="onboarding_start_fresh">새로 시작</string>
|
||||||
|
<string name="onboarding_join_existing">기존 집 참여</string>
|
||||||
|
<string name="onboarding_already_have_account">이미 계정이 있으신가요? 로그인</string>
|
||||||
|
<string name="onboarding_skip">건너뛰기</string>
|
||||||
|
<string name="onboarding_continue">계속</string>
|
||||||
|
<string name="onboarding_get_started">시작하기</string>
|
||||||
|
<string name="onboarding_feature_tasks_title">작업을 절대 잊지 마세요</string>
|
||||||
|
<string name="onboarding_feature_tasks_desc">스마트 알림과 함께 모든 집 관리 작업을 한곳에서 관리하세요</string>
|
||||||
|
<string name="onboarding_feature_docs_title">손끝에서 보는 문서</string>
|
||||||
|
<string name="onboarding_feature_docs_desc">보증서, 설명서, 영수증을 안전하게 보관하고 언제든 확인하세요</string>
|
||||||
|
<string name="onboarding_feature_contractors_title">믿을 수 있는 시공업체</string>
|
||||||
|
<string name="onboarding_feature_contractors_desc">모든 시공업체 연락처를 정리하고 쉽게 이용하세요</string>
|
||||||
|
<string name="onboarding_feature_family_title">가족과 공유</string>
|
||||||
|
<string name="onboarding_feature_family_desc">가족을 초대하여 집 관리를 함께하세요</string>
|
||||||
|
<string name="onboarding_feature_notifications_title">스마트 알림</string>
|
||||||
|
<string name="onboarding_feature_notifications_desc">알림에서 바로 작업을 완료할 수 있는 실행 가능한 알림을 받으세요</string>
|
||||||
|
<string name="onboarding_feature_widgets_title">홈 화면 위젯</string>
|
||||||
|
<string name="onboarding_feature_widgets_desc">홈 화면에서 바로 작업과 알림에 빠르게 접근하세요</string>
|
||||||
|
<string name="onboarding_location_title">집이 어디에 있나요?</string>
|
||||||
|
<string name="onboarding_location_subtitle">지역 기후에 맞는 관리 작업을 추천해 드립니다</string>
|
||||||
|
<string name="onboarding_location_use_my_location">내 위치 사용</string>
|
||||||
|
<string name="onboarding_location_detecting">감지 중...</string>
|
||||||
|
<string name="onboarding_location_enter_zip">대신 우편번호 입력</string>
|
||||||
|
<string name="onboarding_location_enter_zip_prompt">우편번호를 입력하세요</string>
|
||||||
|
<string name="onboarding_location_zip_placeholder">우편번호</string>
|
||||||
|
<string name="onboarding_name_residence_title">집 이름 짓기</string>
|
||||||
|
<string name="onboarding_name_residence_subtitle">자산을 구분할 수 있도록 이름을 지어 주세요</string>
|
||||||
|
<string name="onboarding_name_residence_placeholder">예: 우리 집, 바닷가 별장, 아파트</string>
|
||||||
|
<string name="onboarding_name_residence_hint">자세한 내용은 나중에 추가할 수 있습니다</string>
|
||||||
|
<string name="onboarding_create_account_title">내 집 저장하기</string>
|
||||||
|
<string name="onboarding_create_account_subtitle">여러 기기에서 동기화하려면 계정을 만드세요</string>
|
||||||
|
<string name="onboarding_create_with_email">이메일로 계정 만들기</string>
|
||||||
|
<string name="onboarding_verify_email_title">이메일 인증</string>
|
||||||
|
<string name="onboarding_verify_email_subtitle">이메일로 6자리 코드를 보냈습니다. 아래에 입력하여 계정을 인증하세요.</string>
|
||||||
|
<string name="onboarding_verify_email_hint">코드를 받지 못하셨나요? 스팸함을 확인하세요</string>
|
||||||
|
<string name="onboarding_join_title">거주지 참여</string>
|
||||||
|
<string name="onboarding_join_subtitle">공유받은 6자리 코드를 입력하여 기존 집에 참여하세요</string>
|
||||||
|
<string name="onboarding_join_placeholder">공유 코드 입력</string>
|
||||||
|
<string name="onboarding_join_button">거주지 참여</string>
|
||||||
|
<string name="onboarding_tasks_title">모든 준비가 끝났어요!</string>
|
||||||
|
<string name="onboarding_tasks_subtitle">몇 가지 작업으로 시작해 볼까요. 많이 선택할수록 더 잘 기억하도록 도와드릴게요!</string>
|
||||||
|
<string name="onboarding_tasks_selected">%1$d/%2$d개 작업 선택됨</string>
|
||||||
|
<string name="onboarding_tasks_add_popular">인기 작업 추가</string>
|
||||||
|
<string name="onboarding_tasks_continue">작업 %1$d개 추가하고 계속</string>
|
||||||
|
<string name="onboarding_tasks_skip">나중에 하기</string>
|
||||||
|
<string name="onboarding_category_hvac">냉난방 및 공조</string>
|
||||||
|
<string name="onboarding_category_safety">안전 및 보안</string>
|
||||||
|
<string name="onboarding_category_plumbing">배관</string>
|
||||||
|
<string name="onboarding_category_outdoor">야외 및 잔디</string>
|
||||||
|
<string name="onboarding_category_appliances">가전제품</string>
|
||||||
|
<string name="onboarding_category_general">일반 가정</string>
|
||||||
|
<string name="onboarding_subscription_title">Pro로 전환</string>
|
||||||
|
<string name="onboarding_subscription_subtitle">집 관리를 한 단계 업그레이드하세요</string>
|
||||||
|
<string name="onboarding_subscription_pro">HONEYDUE PRO</string>
|
||||||
|
<string name="onboarding_subscription_social_proof">4.9 • 주택 소유자 1만 명 이상</string>
|
||||||
|
<string name="onboarding_subscription_benefit_properties">무제한 자산</string>
|
||||||
|
<string name="onboarding_subscription_benefit_properties_desc">소유한 모든 집을 관리하세요</string>
|
||||||
|
<string name="onboarding_subscription_benefit_reminders">스마트 알림</string>
|
||||||
|
<string name="onboarding_subscription_benefit_reminders_desc">관리 마감일을 놓치지 마세요</string>
|
||||||
|
<string name="onboarding_subscription_benefit_documents">문서 보관함</string>
|
||||||
|
<string name="onboarding_subscription_benefit_documents_desc">모든 문서를 한곳에 보관하세요</string>
|
||||||
|
<string name="onboarding_subscription_benefit_family">가족 공유</string>
|
||||||
|
<string name="onboarding_subscription_benefit_family_desc">모두가 같은 정보를 공유하세요</string>
|
||||||
|
<string name="onboarding_subscription_benefit_insights">지출 분석</string>
|
||||||
|
<string name="onboarding_subscription_benefit_insights_desc">돈이 어디에 쓰이는지 확인하세요</string>
|
||||||
|
<string name="onboarding_subscription_benefit_contractor_sharing">시공업체 공유</string>
|
||||||
|
<string name="onboarding_subscription_benefit_contractor_sharing_desc">신뢰하는 시공업체를 가족과 친구와 공유하세요</string>
|
||||||
|
<string name="onboarding_subscription_benefit_widgets">홈 화면 위젯</string>
|
||||||
|
<string name="onboarding_subscription_benefit_widgets_desc">홈 화면에서 바로 빠른 작업을 실행하세요</string>
|
||||||
|
<string name="onboarding_subscription_benefit_notifications">실행 가능한 알림</string>
|
||||||
|
<string name="onboarding_subscription_benefit_notifications_desc">알림에서 바로 작업을 완료하세요</string>
|
||||||
|
<string name="onboarding_subscription_choose_plan">요금제 선택</string>
|
||||||
|
<string name="onboarding_subscription_monthly">월간</string>
|
||||||
|
<string name="onboarding_subscription_yearly">연간</string>
|
||||||
|
<string name="onboarding_subscription_save">30% 절약</string>
|
||||||
|
<string name="onboarding_subscription_monthly_price">월 $2.99</string>
|
||||||
|
<string name="onboarding_subscription_yearly_price">연 $23.99</string>
|
||||||
|
<string name="onboarding_subscription_yearly_monthly">월 $1.99</string>
|
||||||
|
<string name="onboarding_subscription_start_trial">7일 무료 체험 시작</string>
|
||||||
|
<string name="onboarding_subscription_continue_free">무료로 계속</string>
|
||||||
|
<string name="onboarding_subscription_trial_terms">7일 무료 체험 후 %1$s. 언제든 취소 가능.</string>
|
||||||
|
<string name="onboarding_home_profile_title">집에 대해 알려 주세요</string>
|
||||||
|
<string name="onboarding_home_profile_subtitle">모두 선택 사항이며 맞춤 관리 계획에 도움이 됩니다</string>
|
||||||
|
<string name="onboarding_home_profile_systems">시스템</string>
|
||||||
|
<string name="onboarding_home_profile_features">특징</string>
|
||||||
|
<string name="onboarding_home_profile_exterior">외부</string>
|
||||||
|
<string name="onboarding_home_profile_interior">내부</string>
|
||||||
|
<string name="for_you_tab">추천</string>
|
||||||
|
<string name="browse_tab">둘러보기</string>
|
||||||
|
<string name="biometric_lock_title">앱 잠김</string>
|
||||||
|
<string name="biometric_lock_description">honeyDue 잠금을 해제하려면 인증하세요</string>
|
||||||
|
<string name="biometric_lock_setting_title">생체 인식 잠금</string>
|
||||||
|
<string name="biometric_lock_setting_subtitle">앱을 열 때 인증 요구</string>
|
||||||
|
<string name="biometric_prompt_title">honeyDue 잠금 해제</string>
|
||||||
|
<string name="biometric_prompt_subtitle">계속하려면 본인 인증을 하세요</string>
|
||||||
|
<string name="biometric_unlock_button">생체 인식으로 잠금 해제</string>
|
||||||
|
<string name="biometric_auth_failed">인증에 실패했습니다</string>
|
||||||
|
<string name="biometric_not_available">이 기기에서는 생체 인증을 사용할 수 없습니다</string>
|
||||||
|
<string name="notif_channel_task_reminder_name">작업 알림</string>
|
||||||
|
<string name="notif_channel_task_reminder_description">예정 및 임박한 작업 알림</string>
|
||||||
|
<string name="notif_channel_task_overdue_name">연체된 작업</string>
|
||||||
|
<string name="notif_channel_task_overdue_description">작업 마감일이 지나면 알림</string>
|
||||||
|
<string name="notif_channel_residence_invite_name">거주지 초대</string>
|
||||||
|
<string name="notif_channel_residence_invite_description">공유 거주지 참여 초대</string>
|
||||||
|
<string name="notif_channel_subscription_name">구독 업데이트</string>
|
||||||
|
<string name="notif_channel_subscription_description">구독 상태 및 결제 업데이트</string>
|
||||||
|
<string name="common_selected">선택됨</string>
|
||||||
|
<string name="common_open">열기</string>
|
||||||
|
<string name="common_skip">건너뛰기</string>
|
||||||
|
<string name="common_or">또는</string>
|
||||||
|
<string name="common_info">정보</string>
|
||||||
|
<string name="common_verified">확인됨</string>
|
||||||
|
<string name="common_warning">경고</string>
|
||||||
|
<string name="common_photo">사진</string>
|
||||||
|
<string name="common_included">포함됨</string>
|
||||||
|
<string name="common_verifying">확인 중…</string>
|
||||||
|
<string name="time_am">오전</string>
|
||||||
|
<string name="time_pm">오후</string>
|
||||||
|
<string name="time_eve">저녁</string>
|
||||||
|
<string name="error_network_title">네트워크 오류</string>
|
||||||
|
<string name="image_failed_to_load">불러오기 실패</string>
|
||||||
|
<string name="theme_appearance_title">모양</string>
|
||||||
|
<string name="theme_use_system_colors">시스템 색상 사용</string>
|
||||||
|
<string name="theme_material_you_desc">Android 12 이상의 Material You 따르기(배경화면 색상)</string>
|
||||||
|
<string name="paywall_choose_plan_title">플랜 선택</string>
|
||||||
|
<string name="paywall_choose_plan_subtitle">Pro로 업그레이드하여 무제한 이용</string>
|
||||||
|
<string name="paywall_upgrade_to_pro">Pro로 업그레이드</string>
|
||||||
|
<string name="paywall_col_feature">기능</string>
|
||||||
|
<string name="paywall_col_free">무료</string>
|
||||||
|
<string name="paywall_col_pro">Pro</string>
|
||||||
|
<string name="paywall_feat_properties">부동산</string>
|
||||||
|
<string name="paywall_feat_tasks">작업</string>
|
||||||
|
<string name="paywall_feat_contractors">시공업체</string>
|
||||||
|
<string name="paywall_feat_documents">문서</string>
|
||||||
|
<string name="paywall_val_1_property">부동산 1개</string>
|
||||||
|
<string name="paywall_val_10_tasks">작업 10개</string>
|
||||||
|
<string name="paywall_val_unlimited">무제한</string>
|
||||||
|
<string name="paywall_val_not_available">사용 불가</string>
|
||||||
|
<string name="reset_check_email_title">이메일을 확인하세요</string>
|
||||||
|
<string name="reset_sent_code_to">6자리 코드를 다음 주소로 보냈습니다</string>
|
||||||
|
<string name="reset_code_expires">코드는 15분 후에 만료됩니다</string>
|
||||||
|
<string name="reset_enter_code_hint">이메일로 받은 6자리 코드를 입력하세요</string>
|
||||||
|
<string name="reset_code_verified_msg">코드가 확인되었습니다! 이제 새 비밀번호를 설정하세요</string>
|
||||||
|
<string name="reset_didnt_receive">코드를 받지 못하셨나요?</string>
|
||||||
|
<string name="reset_check_spam">보이지 않으면 스팸 폴더를 확인하세요</string>
|
||||||
|
<string name="reset_verify_failed_title">코드 확인 실패</string>
|
||||||
|
<string name="verify_email_required_msg">이메일 인증이 필요합니다. 받은 편지함에서 6자리 코드를 확인하세요.</string>
|
||||||
|
<string name="verify_email_invalid_code">유효한 6자리 코드를 입력하세요</string>
|
||||||
|
<string name="verify_email_didnt_receive">코드를 받지 못하셨나요? 스팸 폴더를 확인하거나 고객지원에 문의하세요.</string>
|
||||||
|
<string name="verify_email_failed_title">인증 실패</string>
|
||||||
|
<string name="reset_pw_success_msg">비밀번호가 성공적으로 재설정되었습니다</string>
|
||||||
|
<string name="reset_pw_can_login">이제 새 비밀번호로 로그인할 수 있습니다</string>
|
||||||
|
<string name="reset_return_to_login">로그인으로 돌아가기</string>
|
||||||
|
<string name="reset_set_new_pw_title">새 비밀번호 설정</string>
|
||||||
|
<string name="reset_create_strong_pw">계정을 보호하기 위해 강력한 비밀번호를 만드세요</string>
|
||||||
|
<string name="reset_pw_requirements">비밀번호 요건</string>
|
||||||
|
<string name="reset_pw_failed_title">비밀번호 재설정 실패</string>
|
||||||
|
<string name="forgot_send_code_hint">이 주소로 6자리 인증 코드를 보내드립니다</string>
|
||||||
|
<string name="forgot_check_email_msg">6자리 인증 코드가 있는지 이메일을 확인하세요</string>
|
||||||
|
<string name="forgot_back_to_login">비밀번호가 기억나셨나요? 로그인으로 돌아가기</string>
|
||||||
|
<string name="forgot_send_failed_title">재설정 코드 전송 실패</string>
|
||||||
|
<string name="join_property_title">부동산 참여</string>
|
||||||
|
<string name="join_shared_property_header">공유된 부동산 참여</string>
|
||||||
|
<string name="join_enter_code_desc">소유자가 제공한 6자리 공유 코드를 입력하세요.</string>
|
||||||
|
<string name="join_share_code_label">공유 코드</string>
|
||||||
|
<string name="join_share_code_placeholder">ABC123</string>
|
||||||
|
<string name="join_code_helper">코드는 대문자 6자리입니다</string>
|
||||||
|
<string name="join_joining">참여 중…</string>
|
||||||
|
<string name="biometric_enter_pin">잠금 해제하려면 PIN을 입력하세요</string>
|
||||||
|
<string name="biometric_pin_label">4자리 PIN</string>
|
||||||
|
<string name="biometric_incorrect_pin">잘못된 PIN</string>
|
||||||
|
<string name="biometric_unlock">잠금 해제</string>
|
||||||
|
<string name="tasks_create_failed_title">작업 생성 실패</string>
|
||||||
|
<string name="tasks_all_title">모든 작업</string>
|
||||||
|
<string name="tasks_add">작업 추가</string>
|
||||||
|
<string name="tasks_load_failed_title">작업 불러오기 실패</string>
|
||||||
|
<string name="tasks_all_empty_subtitle">첫 작업을 만들어 시작하세요</string>
|
||||||
|
<string name="tasks_add_property_first">먼저 거주지 탭에서 부동산을 추가하세요</string>
|
||||||
|
<string name="residences_upgrade_to_add">업그레이드하여 추가</string>
|
||||||
|
<string name="residences_primary_cd">주 거주지</string>
|
||||||
|
<string name="residence_unit_label">세대: %1$s</string>
|
||||||
|
<string name="residence_error_loading_tasks">작업 불러오기 오류: %1$s</string>
|
||||||
|
<string name="residence_error_loading_contractors">시공업체 불러오기 오류: %1$s</string>
|
||||||
|
<string name="profile_subscription_cd">구독</string>
|
||||||
|
<string name="onboarding_sub_cancel_note">설정에서 언제든지 취소 가능 • 약정 없음</string>
|
||||||
|
<string name="onboarding_joining_residence">거주지 참여 중…</string>
|
||||||
|
<string name="onboarding_sub_trial_legal">7일 무료 체험 후 %1$s</string>
|
||||||
|
<string name="common_collapse">접기</string>
|
||||||
|
<string name="common_expand">펼치기</string>
|
||||||
|
<string name="common_not_selected">선택 안 됨</string>
|
||||||
|
<string name="auth_logging_in">로그인 중…</string>
|
||||||
|
<string name="auth_requirement_met">요건 충족</string>
|
||||||
|
<string name="auth_requirement_not_met">요건 미충족</string>
|
||||||
|
<string name="error_something_wrong">문제가 발생했습니다</string>
|
||||||
|
<string name="paywall_feat_not_included">포함되지 않음</string>
|
||||||
|
<string name="completions_contractor_prefix">시공업체:</string>
|
||||||
|
<string name="completions_completed_by_prefix">완료한 사람:</string>
|
||||||
|
<string name="completions_remove_photo">사진 제거</string>
|
||||||
|
<string name="completions_star_rating_cd">별 %1$d개</string>
|
||||||
|
<string name="photos_completion_title">완료 사진</string>
|
||||||
|
<string name="photos_task_completion_cd">작업 완료 사진</string>
|
||||||
|
<string name="documents_residence_ref">거주지 #%1$d</string>
|
||||||
|
<string name="documents_task_ref">작업 #%1$d</string>
|
||||||
|
<string name="documents_image_index">이미지 %1$d / %2$d</string>
|
||||||
|
<string name="documents_images_title">문서 이미지</string>
|
||||||
|
<string name="documents_empty_no_warranties">보증을 찾을 수 없습니다</string>
|
||||||
|
<string name="documents_empty_no_documents">문서를 찾을 수 없습니다</string>
|
||||||
|
<string name="documents_days_remaining_count">%1$d일 남음</string>
|
||||||
|
<string name="contractors_property_ref">부동산 #</string>
|
||||||
|
<string name="residence_share_failed">거주지 공유 실패</string>
|
||||||
|
<string name="theme_default">기본</string>
|
||||||
|
<string name="theme_default_desc">선명한 iOS 시스템 색상</string>
|
||||||
|
<string name="theme_teal">청록</string>
|
||||||
|
<string name="theme_teal_desc">따뜻한 강조색의 청록색</string>
|
||||||
|
<string name="theme_ocean">오션</string>
|
||||||
|
<string name="theme_ocean_desc">깊은 블루와 코랄 톤</string>
|
||||||
|
<string name="theme_forest">포레스트</string>
|
||||||
|
<string name="theme_forest_desc">어스 그린과 황금빛 색조</string>
|
||||||
|
<string name="theme_sunset">선셋</string>
|
||||||
|
<string name="theme_sunset_desc">따뜻한 오렌지와 레드</string>
|
||||||
|
<string name="theme_monochrome">모노크롬</string>
|
||||||
|
<string name="theme_monochrome_desc">우아한 그레이스케일</string>
|
||||||
|
<string name="theme_lavender">라벤더</string>
|
||||||
|
<string name="theme_lavender_desc">핑크 강조색의 부드러운 퍼플</string>
|
||||||
|
<string name="theme_crimson">크림슨</string>
|
||||||
|
<string name="theme_crimson_desc">따뜻한 하이라이트의 강렬한 레드</string>
|
||||||
|
<string name="theme_midnight">미드나이트</string>
|
||||||
|
<string name="theme_midnight_desc">스카이 블루가 어우러진 짙은 네이비</string>
|
||||||
|
<string name="theme_desert">데저트</string>
|
||||||
|
<string name="theme_desert_desc">따뜻한 테라코타와 모래 톤</string>
|
||||||
|
<string name="theme_mint">민트</string>
|
||||||
|
<string name="theme_mint_desc">터쿠아즈가 어우러진 상쾌한 그린</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -472,4 +472,585 @@
|
|||||||
<string name="subscription_features">Premium-functies</string>
|
<string name="subscription_features">Premium-functies</string>
|
||||||
<string name="subscription_limit_properties">U hebt de eigendomslimiet voor uw abonnement bereikt</string>
|
<string name="subscription_limit_properties">U hebt de eigendomslimiet voor uw abonnement bereikt</string>
|
||||||
<string name="subscription_limit_tasks">U hebt de taakl imiet voor uw abonnement bereikt</string>
|
<string name="subscription_limit_tasks">U hebt de taakl imiet voor uw abonnement bereikt</string>
|
||||||
|
<string name="home_profile_heating">Verwarming</string>
|
||||||
|
<string name="home_profile_cooling">Koeling</string>
|
||||||
|
<string name="home_profile_water_heater">Boiler</string>
|
||||||
|
<string name="home_profile_pool">Zwembad</string>
|
||||||
|
<string name="home_profile_sprinkler_system">Sproei-installatie</string>
|
||||||
|
<string name="home_profile_fireplace">Open haard</string>
|
||||||
|
<string name="home_profile_garage">Garage</string>
|
||||||
|
<string name="home_profile_basement">Kelder</string>
|
||||||
|
<string name="home_profile_attic">Zolder</string>
|
||||||
|
<string name="home_profile_septic">Septische tank</string>
|
||||||
|
<string name="home_profile_roof_type">Daktype</string>
|
||||||
|
<string name="home_profile_exterior">Buitenkant</string>
|
||||||
|
<string name="home_profile_flooring">Vloer</string>
|
||||||
|
<string name="home_profile_landscaping">Tuinaanleg</string>
|
||||||
|
<string name="onboarding_first_task_selected_count">%1$d taken geselecteerd</string>
|
||||||
|
<string name="onboarding_first_task_add_continue">%1$d taken toevoegen & doorgaan</string>
|
||||||
|
<string name="onboarding_first_task_finding">Taken zoeken voor je woning...</string>
|
||||||
|
<string name="onboarding_first_task_no_suggestions">Nog geen persoonlijke suggesties — bekijk de volledige catalogus of sla deze stap over.</string>
|
||||||
|
<string name="onboarding_first_task_browse_all">Alles bekijken</string>
|
||||||
|
<string name="onboarding_first_task_skip">Overslaan</string>
|
||||||
|
<string name="onboarding_first_task_suggestions_error">Je suggesties konden niet worden geladen</string>
|
||||||
|
<string name="onboarding_first_task_connection_error">Controleer je verbinding en probeer het opnieuw.</string>
|
||||||
|
<string name="onboarding_first_task_loading_catalog">De taakcatalogus laden...</string>
|
||||||
|
<string name="onboarding_first_task_catalog_error">De taakcatalogus kon niet worden geladen</string>
|
||||||
|
<string name="onboarding_first_task_no_templates">Op dit moment geen sjablonen beschikbaar.</string>
|
||||||
|
<string name="onboarding_first_task_selected">Geselecteerd</string>
|
||||||
|
<string name="onboarding_first_task_offline">Offline</string>
|
||||||
|
<string name="onboarding_first_task_skip_for_now">Nu overslaan</string>
|
||||||
|
<string name="upgrade_hero_title">Upgraden naar Pro</string>
|
||||||
|
<string name="upgrade_hero_subtitle">Benut het volledige potentieel van honeyDue</string>
|
||||||
|
<string name="upgrade_choose_plan">Kies je abonnement</string>
|
||||||
|
<string name="upgrade_plan_yearly">Jaarlijks</string>
|
||||||
|
<string name="upgrade_plan_monthly">Maandelijks</string>
|
||||||
|
<string name="upgrade_plan_save_50">Bespaar 50%</string>
|
||||||
|
<string name="upgrade_best_value">BESTE KEUZE</string>
|
||||||
|
<string name="upgrade_whats_included">Wat is inbegrepen</string>
|
||||||
|
<string name="upgrade_feature_unlimited_properties">Onbeperkt aantal woningen</string>
|
||||||
|
<string name="upgrade_feature_unlimited_properties_desc">Houd onderhoud bij voor al je woningen</string>
|
||||||
|
<string name="upgrade_feature_unlimited_tasks">Onbeperkt aantal taken</string>
|
||||||
|
<string name="upgrade_feature_unlimited_tasks_desc">Vergeet nooit meer een onderhoudstaak</string>
|
||||||
|
<string name="upgrade_feature_contractor_management">Vakmensbeheer</string>
|
||||||
|
<string name="upgrade_feature_contractor_management_desc">Bewaar en beoordeel je vertrouwde vakmensen</string>
|
||||||
|
<string name="upgrade_feature_document_vault">Documentkluis</string>
|
||||||
|
<string name="upgrade_feature_document_vault_desc">Bewaar garanties, bonnen en handleidingen</string>
|
||||||
|
<string name="upgrade_feature_family_sharing">Delen met gezin</string>
|
||||||
|
<string name="upgrade_feature_family_sharing_desc">Nodig gezinsleden uit om samen te werken</string>
|
||||||
|
<string name="upgrade_feature_smart_reminders">Slimme herinneringen</string>
|
||||||
|
<string name="upgrade_feature_smart_reminders_desc">Ontvang een melding wanneer taken aflopen</string>
|
||||||
|
<string name="upgrade_feature_document_warranty_storage">Opslag voor documenten & garanties</string>
|
||||||
|
<string name="upgrade_subscribe_now">Nu abonneren</string>
|
||||||
|
<string name="upgrade_restore_purchases">Aankopen herstellen</string>
|
||||||
|
<string name="upgrade_terms_text">Het abonnement wordt automatisch verlengd, tenzij het minstens 24 uur voor het einde van de huidige periode wordt opgezegd. Beheer abonnementen in de instellingen van je apparaat.</string>
|
||||||
|
<string name="upgrade_terms_of_use">Gebruiksvoorwaarden</string>
|
||||||
|
<string name="upgrade_included">Inbegrepen</string>
|
||||||
|
<string name="upgrade_compare_free_vs_pro">Vergelijk Gratis met Pro</string>
|
||||||
|
<string name="upgrade_maybe_later">Misschien later</string>
|
||||||
|
<string name="upgrade_warning">Waarschuwing</string>
|
||||||
|
<string name="upgrade_subscription_active">Abonnement actief</string>
|
||||||
|
<string name="upgrade_subscription_active_message">Je hebt nu volledige toegang tot alle Pro-functies!</string>
|
||||||
|
<string name="upgrade_feature_required_title">Upgrade vereist</string>
|
||||||
|
<string name="upgrade_feature_required_message">Deze functie is beschikbaar met een Pro-abonnement.</string>
|
||||||
|
<string name="upgrade_prompt_default_message">Ontgrendel onbeperkte toegang tot alle functies</string>
|
||||||
|
<string name="upgrade_product_monthly_name">honeyDue Pro Maandelijks</string>
|
||||||
|
<string name="upgrade_product_annual_name">honeyDue Pro Jaarlijks</string>
|
||||||
|
<string name="upgrade_billed_monthly">Maandelijks gefactureerd</string>
|
||||||
|
<string name="upgrade_billed_annually">Jaarlijks gefactureerd</string>
|
||||||
|
<string name="upgrade_save_22">Bespaar 22%</string>
|
||||||
|
<string name="tasks_column_done">Klaar</string>
|
||||||
|
<string name="tasks_column_archived">Gearchiveerd</string>
|
||||||
|
<string name="tasks_column_empty">Geen taken</string>
|
||||||
|
<string name="tasks_new_title">Nieuwe taak</string>
|
||||||
|
<string name="tasks_title_field_label">Titel</string>
|
||||||
|
<string name="tasks_title_placeholder">bijv. Boiler doorspoelen</string>
|
||||||
|
<string name="tasks_description_placeholder">Optionele details</string>
|
||||||
|
<string name="tasks_due_date_optional_label">Vervaldatum (optioneel)</string>
|
||||||
|
<string name="tasks_due_date_placeholder_format">jjjj-MM-dd</string>
|
||||||
|
<string name="tasks_due_date_blank_helper">Laat leeg voor geen vervaldatum</string>
|
||||||
|
<string name="tasks_estimated_cost_optional_label">Geschatte kosten (optioneel)</string>
|
||||||
|
<string name="suggestions_title">Voorgestelde taken</string>
|
||||||
|
<string name="suggestions_skip">Overslaan</string>
|
||||||
|
<string name="suggestions_accept">Accepteren</string>
|
||||||
|
<string name="suggestions_load_failed">Suggesties konden niet worden geladen</string>
|
||||||
|
<string name="suggestions_empty_title">Nog geen suggesties</string>
|
||||||
|
<string name="suggestions_empty_subtitle">Vul je woningprofiel in om persoonlijke aanbevelingen te zien.</string>
|
||||||
|
<string name="completion_history_title">Voltooiingsgeschiedenis</string>
|
||||||
|
<string name="completion_history_count_one">%1$d voltooiing</string>
|
||||||
|
<string name="completion_history_count_other">%1$d voltooiingen</string>
|
||||||
|
<string name="completion_history_loading">Voltooiingen laden...</string>
|
||||||
|
<string name="completion_history_load_failed">Voltooiingen laden mislukt</string>
|
||||||
|
<string name="completion_history_empty_title">Nog geen voltooiingen</string>
|
||||||
|
<string name="completion_history_empty_message">Deze taak is nog niet voltooid.</string>
|
||||||
|
<string name="completion_history_completed_by">Voltooid door %1$s</string>
|
||||||
|
<string name="completion_history_view_photo">Foto bekijken</string>
|
||||||
|
<string name="manage_users_remove_user">Gebruiker verwijderen</string>
|
||||||
|
<string name="manage_users_copy_code">Code kopiëren</string>
|
||||||
|
<string name="manage_users_code_copied">Code gekopieerd naar klembord</string>
|
||||||
|
<string name="manage_users_load_failed">Gebruikers konden niet worden geladen</string>
|
||||||
|
<string name="manage_users_remove_confirm">%1$s uit deze woning verwijderen?</string>
|
||||||
|
<string name="properties_shared_users_count">Gedeelde gebruikers (%1$d)</string>
|
||||||
|
<string name="properties_no_shared_users">Geen gedeelde gebruikers</string>
|
||||||
|
<string name="properties_shared_users_helper">Gebruikers met toegang tot deze woning. Gebruik de deelknop om anderen uit te nodigen.</string>
|
||||||
|
<string name="properties_remove_user_confirm">Weet je zeker dat je %1$s uit deze woning wilt verwijderen?</string>
|
||||||
|
<string name="properties_remove_button">Verwijderen</string>
|
||||||
|
<string name="documents_expires_label">Verloopt</string>
|
||||||
|
<string name="auth_password_requirement_length">Minstens 8 tekens</string>
|
||||||
|
<string name="auth_password_requirement_uppercase">Bevat een hoofdletter</string>
|
||||||
|
<string name="auth_password_requirement_lowercase">Bevat een kleine letter</string>
|
||||||
|
<string name="auth_password_requirement_digit">Bevat een cijfer</string>
|
||||||
|
<string name="auth_password_requirement_match">Wachtwoorden komen overeen</string>
|
||||||
|
<string name="auth_password_requirements_title">Wachtwoordvereisten</string>
|
||||||
|
<string name="auth_password_complexity_error">Wachtwoord moet minstens 8 tekens bevatten, met ten minste één hoofdletter, één kleine letter en één cijfer</string>
|
||||||
|
<string name="properties_join_residence_title">Woning toetreden</string>
|
||||||
|
<string name="properties_join_residence_message">Wil je deze gedeelde woning toetreden?</string>
|
||||||
|
<string name="properties_join_success">Toegetreden tot woning</string>
|
||||||
|
<string name="properties_join_success_message">Je hebt nu toegang tot %1$s.</string>
|
||||||
|
<string name="properties_join_failed">Toetreden mislukt</string>
|
||||||
|
<string name="properties_joining">Toetreden...</string>
|
||||||
|
<string name="properties_shared_by">Gedeeld door: %1$s</string>
|
||||||
|
<string name="properties_expires">Verloopt: %1$s</string>
|
||||||
|
<string name="properties_share_upgrade_title">Pro-functie</string>
|
||||||
|
<string name="properties_share_upgrade_message">Woningen delen is een Pro-functie. Upgrade om gezinsleden uit te nodigen voor samenwerking aan woningonderhoud.</string>
|
||||||
|
<string name="tasks_failed_to_cancel">Taak annuleren mislukt</string>
|
||||||
|
<string name="tasks_failed_to_restore">Taak herstellen mislukt</string>
|
||||||
|
<string name="tasks_failed_to_mark_in_progress">Taak markeren als bezig mislukt</string>
|
||||||
|
<string name="tasks_failed_to_archive">Taak archiveren mislukt</string>
|
||||||
|
<string name="tasks_failed_to_unarchive">Taak dearchiveren mislukt</string>
|
||||||
|
<string name="tasks_card_in_progress">BEZIG</string>
|
||||||
|
<string name="tasks_card_actions">Acties</string>
|
||||||
|
<string name="tasks_card_mark_in_progress">Markeren als bezig</string>
|
||||||
|
<string name="tasks_card_complete_task">Taak voltooien</string>
|
||||||
|
<string name="tasks_card_edit_task">Taak bewerken</string>
|
||||||
|
<string name="tasks_card_cancel_task">Taak annuleren</string>
|
||||||
|
<string name="tasks_card_restore_task">Taak herstellen</string>
|
||||||
|
<string name="tasks_card_archive_task">Taak archiveren</string>
|
||||||
|
<string name="tasks_card_unarchive_task">Taak dearchiveren</string>
|
||||||
|
<string name="tasks_card_not_available">N.v.t.</string>
|
||||||
|
<string name="tasks_card_completed_by">Door: %1$s</string>
|
||||||
|
<string name="tasks_card_cost">Kosten: $%1$s</string>
|
||||||
|
<string name="tasks_card_view_photos">Foto\'s bekijken (%1$d)</string>
|
||||||
|
<string name="tasks_add_new">Nieuwe taak toevoegen</string>
|
||||||
|
<string name="tasks_property_required">Woning *</string>
|
||||||
|
<string name="tasks_property_error">Woning is verplicht</string>
|
||||||
|
<string name="tasks_browse_templates">Taaksjablonen bekijken</string>
|
||||||
|
<string name="tasks_common_tasks">%1$d veelvoorkomende taken</string>
|
||||||
|
<string name="tasks_category_error">Categorie is verplicht</string>
|
||||||
|
<string name="tasks_interval_days">Interval in dagen</string>
|
||||||
|
<string name="tasks_interval_override">Standaardfrequentie overschrijven</string>
|
||||||
|
<string name="tasks_custom_interval_help">Aantal dagen tussen elke herhaling</string>
|
||||||
|
<string name="tasks_due_date_format_error">Vervaldatum is verplicht (formaat: JJJJ-MM-DD)</string>
|
||||||
|
<string name="tasks_due_date_format">Formaat: JJJJ-MM-DD</string>
|
||||||
|
<string name="tasks_create">Taak aanmaken</string>
|
||||||
|
<string name="tasks_in_progress_label">Bezig</string>
|
||||||
|
<string name="templates_title">Taaksjablonen</string>
|
||||||
|
<string name="templates_done">Klaar</string>
|
||||||
|
<string name="templates_search_placeholder">Sjablonen zoeken...</string>
|
||||||
|
<string name="templates_clear">Wissen</string>
|
||||||
|
<string name="templates_result">resultaat</string>
|
||||||
|
<string name="templates_results">resultaten</string>
|
||||||
|
<string name="templates_no_results_title">Geen sjablonen gevonden</string>
|
||||||
|
<string name="templates_no_results_message">Probeer een andere zoekterm</string>
|
||||||
|
<string name="templates_empty_title">Geen sjablonen beschikbaar</string>
|
||||||
|
<string name="templates_empty_message">Sjablonen verschijnen hier zodra ze geladen zijn</string>
|
||||||
|
<string name="templates_expand">Uitvouwen</string>
|
||||||
|
<string name="templates_collapse">Invouwen</string>
|
||||||
|
<string name="templates_add">Toevoegen</string>
|
||||||
|
<string name="templates_all_categories">Alle</string>
|
||||||
|
<string name="templates_apply">Toepassen</string>
|
||||||
|
<string name="templates_apply_count">Toepassen (%1$d)</string>
|
||||||
|
<string name="templates_selected_count">%1$d geselecteerd</string>
|
||||||
|
<string name="templates_retry">Opnieuw</string>
|
||||||
|
<string name="templates_load_failed">Sjablonen laden mislukt</string>
|
||||||
|
<string name="templates_create_failed">Taken aanmaken mislukt</string>
|
||||||
|
<string name="completions_complete_task_title">Taak voltooien: %1$s</string>
|
||||||
|
<string name="completions_select_contractor">Aannemer selecteren (optioneel)</string>
|
||||||
|
<string name="completions_choose_contractor_placeholder">Kies een aannemer of laat leeg</string>
|
||||||
|
<string name="completions_expand">Uitvouwen</string>
|
||||||
|
<string name="completions_none_manual">Geen (handmatige invoer)</string>
|
||||||
|
<string name="completions_loading_contractors">Aannemers laden...</string>
|
||||||
|
<string name="completions_error_loading_contractors">Fout bij laden van aannemers</string>
|
||||||
|
<string name="completions_completed_by_name">Voltooid door naam (optioneel)</string>
|
||||||
|
<string name="completions_completed_by_placeholder">Voer naam in als je geen aannemer hierboven gebruikt</string>
|
||||||
|
<string name="completions_actual_cost_optional">Werkelijke kosten (optioneel)</string>
|
||||||
|
<string name="completions_notes_optional">Notities (optioneel)</string>
|
||||||
|
<string name="completions_rating">Beoordeling: %1$d van 5</string>
|
||||||
|
<string name="completions_add_images">Afbeeldingen toevoegen</string>
|
||||||
|
<string name="completions_take_photo">Foto maken</string>
|
||||||
|
<string name="completions_choose_from_library">Kiezen uit bibliotheek</string>
|
||||||
|
<string name="completions_images_selected">%1$d afbeelding(en) geselecteerd</string>
|
||||||
|
<string name="completions_remove_image">Afbeelding verwijderen</string>
|
||||||
|
<string name="completions_complete_button">Voltooien</string>
|
||||||
|
<string name="completions_quality_rating">Kwaliteitsbeoordeling</string>
|
||||||
|
<string name="completions_photos_count">Foto\'s (%1$d/%2$d)</string>
|
||||||
|
<string name="completions_camera">Camera</string>
|
||||||
|
<string name="completions_library">Bibliotheek</string>
|
||||||
|
<string name="completions_add_photos_helper">Voeg foto\'s van voltooid werk toe (optioneel)</string>
|
||||||
|
<string name="completions_contractor_helper">Koppel deze taakvoltooiing aan een aannemer</string>
|
||||||
|
<string name="completions_details_section">Voltooiingsdetails</string>
|
||||||
|
<string name="completions_optional_info">Alle velden zijn optioneel</string>
|
||||||
|
<string name="completions_notes_helper">Voeg notities over het voltooide werk toe</string>
|
||||||
|
<string name="completions_notes_placeholder">Beschrijf het uitgevoerde werk, gevonden problemen, enz.</string>
|
||||||
|
<string name="completions_rate_quality">Beoordeel de kwaliteit van het uitgevoerde werk</string>
|
||||||
|
<string name="completions_enter_manually">Voer hieronder de naam handmatig in</string>
|
||||||
|
<string name="manage_users_title">Gebruikers beheren</string>
|
||||||
|
<string name="manage_users_invite_title">Anderen uitnodigen</string>
|
||||||
|
<string name="manage_users_easy_share">Eenvoudig delen</string>
|
||||||
|
<string name="manage_users_send_invite">Uitnodigingslink versturen</string>
|
||||||
|
<string name="manage_users_easy_share_desc">Stuur een .honeydue-bestand via Berichten, E-mail of delen. Ze tikken er gewoon op om toe te treden.</string>
|
||||||
|
<string name="manage_users_share_code">Deelcode</string>
|
||||||
|
<string name="manage_users_no_code">Geen actieve code</string>
|
||||||
|
<string name="manage_users_generate">Code genereren</string>
|
||||||
|
<string name="manage_users_generate_new">Nieuwe code genereren</string>
|
||||||
|
<string name="manage_users_code_desc">Deel deze code van 6 tekens. Ze kunnen die in de app invoeren om toe te treden.</string>
|
||||||
|
<string name="manage_users_users_count">Gebruikers (%1$d)</string>
|
||||||
|
<string name="manage_users_owner_badge">Eigenaar</string>
|
||||||
|
<string name="manage_users_remove">Verwijderen</string>
|
||||||
|
<string name="manage_users_or">of</string>
|
||||||
|
<string name="contractors_share">Aannemer delen</string>
|
||||||
|
<string name="contractors_share_upgrade_title">Pro-functie</string>
|
||||||
|
<string name="contractors_share_upgrade_message">Aannemers delen is een Pro-functie. Upgrade om je vertrouwde aannemers met vrienden en familie te delen.</string>
|
||||||
|
<string name="contractors_import_title">Aannemer importeren</string>
|
||||||
|
<string name="contractors_import_message">Wil je deze aannemer importeren?</string>
|
||||||
|
<string name="contractors_import_success">Aannemer geïmporteerd</string>
|
||||||
|
<string name="contractors_import_success_message">%1$s is toegevoegd aan je contacten.</string>
|
||||||
|
<string name="contractors_import_failed">Importeren mislukt</string>
|
||||||
|
<string name="contractors_shared_by">Gedeeld door: %1$s</string>
|
||||||
|
<string name="contractors_form_add_title">Aannemer toevoegen</string>
|
||||||
|
<string name="contractors_form_edit_title">Aannemer bewerken</string>
|
||||||
|
<string name="contractors_form_basic_info">Basisgegevens</string>
|
||||||
|
<string name="contractors_form_name_required">Naam *</string>
|
||||||
|
<string name="contractors_form_company">Bedrijf</string>
|
||||||
|
<string name="contractors_form_residence_optional">Woning (optioneel)</string>
|
||||||
|
<string name="contractors_form_personal_no_residence">Persoonlijk (geen woning)</string>
|
||||||
|
<string name="contractors_form_personal_visibility">Alleen jij ziet deze aannemer</string>
|
||||||
|
<string name="contractors_form_shared_visibility">Alle gebruikers van %1$s zien deze aannemer</string>
|
||||||
|
<string name="contractors_form_contact_info">Contactgegevens</string>
|
||||||
|
<string name="contractors_form_phone">Telefoon</string>
|
||||||
|
<string name="contractors_form_email">E-mail</string>
|
||||||
|
<string name="contractors_form_website">Website</string>
|
||||||
|
<string name="contractors_form_specialties">Specialiteiten</string>
|
||||||
|
<string name="contractors_form_address_section">Adres</string>
|
||||||
|
<string name="contractors_form_street_address">Straat en huisnummer</string>
|
||||||
|
<string name="contractors_form_city">Plaats</string>
|
||||||
|
<string name="contractors_form_state">Provincie</string>
|
||||||
|
<string name="contractors_form_zip_code">Postcode</string>
|
||||||
|
<string name="contractors_form_notes_section">Notities</string>
|
||||||
|
<string name="contractors_form_private_notes">Privénotities</string>
|
||||||
|
<string name="contractors_form_mark_favorite">Markeren als favoriet</string>
|
||||||
|
<string name="contractors_form_add_button">Toevoegen</string>
|
||||||
|
<string name="contractors_form_save_button">Opslaan</string>
|
||||||
|
<string name="documents_form_edit_warranty">Garantie bewerken</string>
|
||||||
|
<string name="documents_form_edit_document">Document bewerken</string>
|
||||||
|
<string name="documents_form_add_warranty">Garantie toevoegen</string>
|
||||||
|
<string name="documents_form_add_document">Document toevoegen</string>
|
||||||
|
<string name="documents_form_select_residence">Woning selecteren</string>
|
||||||
|
<string name="documents_form_residence_required">Woning *</string>
|
||||||
|
<string name="documents_form_document_type_required">Documenttype *</string>
|
||||||
|
<string name="documents_form_title_required">Titel *</string>
|
||||||
|
<string name="documents_form_item_name_required">Itemnaam *</string>
|
||||||
|
<string name="documents_form_model_number">Modelnummer</string>
|
||||||
|
<string name="documents_form_serial_number">Serienummer</string>
|
||||||
|
<string name="documents_form_provider_required">Leverancier/bedrijf *</string>
|
||||||
|
<string name="documents_form_provider_contact">Contact leverancier</string>
|
||||||
|
<string name="documents_form_claim_phone">Telefoon voor claims</string>
|
||||||
|
<string name="documents_form_claim_email">E-mail voor claims</string>
|
||||||
|
<string name="documents_form_claim_website">Website voor claims</string>
|
||||||
|
<string name="documents_form_purchase_date">Aankoopdatum (JJJJ-MM-DD)</string>
|
||||||
|
<string name="documents_form_warranty_start">Startdatum garantie (JJJJ-MM-DD)</string>
|
||||||
|
<string name="documents_form_warranty_end_required">Einddatum garantie (JJJJ-MM-DD) *</string>
|
||||||
|
<string name="documents_form_description">Beschrijving</string>
|
||||||
|
<string name="documents_form_category">Categorie</string>
|
||||||
|
<string name="documents_form_select_category">Categorie selecteren</string>
|
||||||
|
<string name="documents_form_category_none">Geen</string>
|
||||||
|
<string name="documents_form_tags">Labels</string>
|
||||||
|
<string name="documents_form_tags_placeholder">label1, label2, label3</string>
|
||||||
|
<string name="documents_form_notes">Notities</string>
|
||||||
|
<string name="documents_form_active">Actief</string>
|
||||||
|
<string name="documents_form_existing_photos">Bestaande foto\'s (%1$d)</string>
|
||||||
|
<string name="documents_form_new_photos">Nieuwe foto\'s (%1$d/%2$d)</string>
|
||||||
|
<string name="documents_form_photos">Foto\'s (%1$d/%2$d)</string>
|
||||||
|
<string name="documents_form_camera">Camera</string>
|
||||||
|
<string name="documents_form_gallery">Galerij</string>
|
||||||
|
<string name="documents_form_image_number">Afbeelding %1$d</string>
|
||||||
|
<string name="documents_form_remove_image">Afbeelding verwijderen</string>
|
||||||
|
<string name="documents_form_update_warranty">Garantie bijwerken</string>
|
||||||
|
<string name="documents_form_update_document">Document bijwerken</string>
|
||||||
|
<string name="documents_form_select_residence_error">Selecteer een woning</string>
|
||||||
|
<string name="documents_form_title_error">Titel is verplicht</string>
|
||||||
|
<string name="documents_form_item_name_error">Itemnaam is verplicht voor garanties</string>
|
||||||
|
<string name="documents_form_provider_error">Leverancier is verplicht voor garanties</string>
|
||||||
|
<string name="documents_form_date_placeholder">2024-01-15</string>
|
||||||
|
<string name="documents_form_date_placeholder_end">2025-01-15</string>
|
||||||
|
<string name="documents_form_failed_to_load_residences">Woningen laden mislukt: %1$s</string>
|
||||||
|
<string name="profile_support">Ondersteuning</string>
|
||||||
|
<string name="profile_contact_support">Contact opnemen met ondersteuning</string>
|
||||||
|
<string name="profile_contact_support_subtitle">Hulp bij je account</string>
|
||||||
|
<string name="profile_upgrade_benefits_title">Ontgrendel premiumfuncties</string>
|
||||||
|
<string name="profile_upgrade_benefits_subtitle">Upgrade naar Pro voor de volledige ervaring</string>
|
||||||
|
<string name="profile_benefit_unlimited_properties">Onbeperkt aantal woningen</string>
|
||||||
|
<string name="profile_benefit_document_vault">Document- en garantieopslag</string>
|
||||||
|
<string name="profile_benefit_residence_sharing">Woningen delen</string>
|
||||||
|
<string name="profile_benefit_contractor_sharing">Aannemers delen</string>
|
||||||
|
<string name="profile_benefit_actionable_notifications">Actiegerichte meldingen</string>
|
||||||
|
<string name="profile_benefit_widgets">Widgets op het startscherm</string>
|
||||||
|
<string name="profile_privacy">Privacybeleid</string>
|
||||||
|
<string name="profile_privacy_subtitle">Bekijk ons privacybeleid</string>
|
||||||
|
<string name="profile_app_version">Versie %1$s</string>
|
||||||
|
<string name="profile_app_name">honeyDue</string>
|
||||||
|
<string name="profile_edit_profile">Profiel bewerken</string>
|
||||||
|
<string name="delete_account_title">Account verwijderen</string>
|
||||||
|
<string name="delete_account_subtitle">Verwijder je account definitief</string>
|
||||||
|
<string name="delete_account_warning">Deze actie is definitief en kan niet ongedaan worden gemaakt. Al je gegevens worden verwijderd.</string>
|
||||||
|
<string name="delete_account_shared_warning">Woningen die je bezit en die met andere gebruikers worden gedeeld, worden ook verwijderd.</string>
|
||||||
|
<string name="delete_account_confirm_password">Voer je wachtwoord in ter bevestiging</string>
|
||||||
|
<string name="delete_account_confirm_type">Typ DELETE ter bevestiging</string>
|
||||||
|
<string name="delete_account_button">Mijn account verwijderen</string>
|
||||||
|
<string name="delete_account_cancel">Annuleren</string>
|
||||||
|
<string name="delete_account_success">Account succesvol verwijderd</string>
|
||||||
|
<string name="delete_account_failed">Account verwijderen mislukt</string>
|
||||||
|
<string name="notifications_daily_digest">Dagelijkse samenvatting</string>
|
||||||
|
<string name="notifications_daily_digest_desc">Dagelijks overzicht van openstaande en achterstallige taken</string>
|
||||||
|
<string name="notifications_email_section">E-mailmeldingen</string>
|
||||||
|
<string name="notifications_email_task_completed">E-mail bij voltooide taak</string>
|
||||||
|
<string name="notifications_email_task_completed_desc">Ontvang een e-mail wanneer een taak is voltooid</string>
|
||||||
|
<string name="notifications_set_custom_time">Aangepaste tijd instellen</string>
|
||||||
|
<string name="notifications_change_time">Wijzigen</string>
|
||||||
|
<string name="notifications_select_time">Meldingstijd selecteren</string>
|
||||||
|
<string name="notifications_master_title">Alle meldingen</string>
|
||||||
|
<string name="notifications_master_desc">Schakel elke categorie met één tik in of uit</string>
|
||||||
|
<string name="notifications_categories_section">Categorieën</string>
|
||||||
|
<string name="notifications_category_task_reminder">Taakherinneringen</string>
|
||||||
|
<string name="notifications_category_task_reminder_desc">Herinneringen voor aankomende en bijna vervallende taken</string>
|
||||||
|
<string name="notifications_category_task_overdue">Achterstallige taken</string>
|
||||||
|
<string name="notifications_category_task_overdue_desc">Waarschuwingen wanneer een taak over de vervaldatum is</string>
|
||||||
|
<string name="notifications_category_residence_invite">Woninguitnodigingen</string>
|
||||||
|
<string name="notifications_category_residence_invite_desc">Uitnodigingen om een gedeelde woning toe te treden</string>
|
||||||
|
<string name="notifications_category_subscription">Abonnementsupdates</string>
|
||||||
|
<string name="notifications_category_subscription_desc">Wijzigingen in facturering en abonnementsstatus</string>
|
||||||
|
<string name="notifications_open_system_settings">Systeeminstellingen openen</string>
|
||||||
|
<string name="notifications_system_settings_desc">Stel geluiden, badges en Niet storen-gedrag nauwkeurig af in de Android-instellingen</string>
|
||||||
|
<string name="common_share">Delen</string>
|
||||||
|
<string name="common_import">Importeren</string>
|
||||||
|
<string name="common_importing">Importeren...</string>
|
||||||
|
<string name="common_try_again">Opnieuw proberen</string>
|
||||||
|
<string name="home_overdue">Achterstallig</string>
|
||||||
|
<string name="home_due_this_week">Deze week vervallen</string>
|
||||||
|
<string name="home_next_30_days">Komende 30 dagen</string>
|
||||||
|
<string name="home_your_properties">Jouw woningen</string>
|
||||||
|
<string name="onboarding_welcome_title">Welkom bij honeyDue</string>
|
||||||
|
<string name="onboarding_welcome_subtitle">Jouw partner voor woningonderhoud</string>
|
||||||
|
<string name="onboarding_start_fresh">Opnieuw beginnen</string>
|
||||||
|
<string name="onboarding_join_existing">Bestaande woning toetreden</string>
|
||||||
|
<string name="onboarding_already_have_account">Heb je al een account? Inloggen</string>
|
||||||
|
<string name="onboarding_skip">Overslaan</string>
|
||||||
|
<string name="onboarding_continue">Doorgaan</string>
|
||||||
|
<string name="onboarding_get_started">Aan de slag</string>
|
||||||
|
<string name="onboarding_feature_tasks_title">Vergeet nooit meer een taak</string>
|
||||||
|
<string name="onboarding_feature_tasks_desc">Houd al je woningonderhoudstaken op één plek bij met slimme herinneringen</string>
|
||||||
|
<string name="onboarding_feature_docs_title">Documenten binnen handbereik</string>
|
||||||
|
<string name="onboarding_feature_docs_desc">Bewaar garanties, handleidingen en bonnen veilig en raadpleeg ze altijd</string>
|
||||||
|
<string name="onboarding_feature_contractors_title">Jouw vertrouwde aannemers</string>
|
||||||
|
<string name="onboarding_feature_contractors_desc">Houd al je aannemerscontacten georganiseerd en eenvoudig toegankelijk</string>
|
||||||
|
<string name="onboarding_feature_family_title">Delen met familie</string>
|
||||||
|
<string name="onboarding_feature_family_desc">Nodig gezinsleden uit om samen aan woningonderhoud te werken</string>
|
||||||
|
<string name="onboarding_feature_notifications_title">Slimme meldingen</string>
|
||||||
|
<string name="onboarding_feature_notifications_desc">Ontvang actiegerichte herinneringen waarmee je taken direct vanuit de melding kunt voltooien</string>
|
||||||
|
<string name="onboarding_feature_widgets_title">Widgets op het startscherm</string>
|
||||||
|
<string name="onboarding_feature_widgets_desc">Snelle toegang tot taken en herinneringen direct vanaf je startscherm</string>
|
||||||
|
<string name="onboarding_location_title">Waar bevindt je woning zich?</string>
|
||||||
|
<string name="onboarding_location_subtitle">We stellen onderhoudstaken voor die passen bij het klimaat in jouw regio</string>
|
||||||
|
<string name="onboarding_location_use_my_location">Mijn locatie gebruiken</string>
|
||||||
|
<string name="onboarding_location_detecting">Detecteren...</string>
|
||||||
|
<string name="onboarding_location_enter_zip">Voer in plaats daarvan postcode in</string>
|
||||||
|
<string name="onboarding_location_enter_zip_prompt">Voer je postcode in</string>
|
||||||
|
<string name="onboarding_location_zip_placeholder">Postcode</string>
|
||||||
|
<string name="onboarding_name_residence_title">Geef je woning een naam</string>
|
||||||
|
<string name="onboarding_name_residence_subtitle">Geef je woning een naam om die makkelijk te herkennen</string>
|
||||||
|
<string name="onboarding_name_residence_placeholder">bijv. Mijn huis, Strandhuis, Appartement</string>
|
||||||
|
<string name="onboarding_name_residence_hint">Je kunt later meer details toevoegen</string>
|
||||||
|
<string name="onboarding_create_account_title">Bewaar je woning</string>
|
||||||
|
<string name="onboarding_create_account_subtitle">Maak een account aan om te synchroniseren tussen apparaten</string>
|
||||||
|
<string name="onboarding_create_with_email">Account aanmaken met e-mail</string>
|
||||||
|
<string name="onboarding_verify_email_title">Verifieer je e-mail</string>
|
||||||
|
<string name="onboarding_verify_email_subtitle">We hebben een 6-cijferige code naar je e-mail gestuurd. Voer die hieronder in om je account te verifiëren.</string>
|
||||||
|
<string name="onboarding_verify_email_hint">Geen code ontvangen? Controleer je spammap</string>
|
||||||
|
<string name="onboarding_join_title">Een woning toetreden</string>
|
||||||
|
<string name="onboarding_join_subtitle">Voer de code van 6 tekens in die met je is gedeeld om een bestaande woning toe te treden</string>
|
||||||
|
<string name="onboarding_join_placeholder">Voer deelcode in</string>
|
||||||
|
<string name="onboarding_join_button">Woning toetreden</string>
|
||||||
|
<string name="onboarding_tasks_title">Je bent helemaal klaar!</string>
|
||||||
|
<string name="onboarding_tasks_subtitle">Laten we beginnen met een paar taken. Hoe meer je kiest, hoe meer we je helpen onthouden!</string>
|
||||||
|
<string name="onboarding_tasks_selected">%1$d/%2$d taken geselecteerd</string>
|
||||||
|
<string name="onboarding_tasks_add_popular">Populairste toevoegen</string>
|
||||||
|
<string name="onboarding_tasks_continue">%1$d taken toevoegen en doorgaan</string>
|
||||||
|
<string name="onboarding_tasks_skip">Voorlopig overslaan</string>
|
||||||
|
<string name="onboarding_category_hvac">Verwarming en klimaat</string>
|
||||||
|
<string name="onboarding_category_safety">Veiligheid en beveiliging</string>
|
||||||
|
<string name="onboarding_category_plumbing">Loodgieterswerk</string>
|
||||||
|
<string name="onboarding_category_outdoor">Buiten en tuin</string>
|
||||||
|
<string name="onboarding_category_appliances">Apparaten</string>
|
||||||
|
<string name="onboarding_category_general">Algemeen onderhoud</string>
|
||||||
|
<string name="onboarding_subscription_title">Word Pro</string>
|
||||||
|
<string name="onboarding_subscription_subtitle">Til je woningbeheer naar een hoger niveau</string>
|
||||||
|
<string name="onboarding_subscription_pro">HONEYDUE PRO</string>
|
||||||
|
<string name="onboarding_subscription_social_proof">4,9 • 10K+ huiseigenaren</string>
|
||||||
|
<string name="onboarding_subscription_benefit_properties">Onbeperkt aantal woningen</string>
|
||||||
|
<string name="onboarding_subscription_benefit_properties_desc">Houd elke woning die je bezit bij</string>
|
||||||
|
<string name="onboarding_subscription_benefit_reminders">Slimme herinneringen</string>
|
||||||
|
<string name="onboarding_subscription_benefit_reminders_desc">Mis nooit meer een onderhoudsdeadline</string>
|
||||||
|
<string name="onboarding_subscription_benefit_documents">Documentenkluis</string>
|
||||||
|
<string name="onboarding_subscription_benefit_documents_desc">Al je documenten op één plek</string>
|
||||||
|
<string name="onboarding_subscription_benefit_family">Delen met familie</string>
|
||||||
|
<string name="onboarding_subscription_benefit_family_desc">Krijg iedereen op één lijn</string>
|
||||||
|
<string name="onboarding_subscription_benefit_insights">Uitgaveninzichten</string>
|
||||||
|
<string name="onboarding_subscription_benefit_insights_desc">Zie waar je geld naartoe gaat</string>
|
||||||
|
<string name="onboarding_subscription_benefit_contractor_sharing">Aannemers delen</string>
|
||||||
|
<string name="onboarding_subscription_benefit_contractor_sharing_desc">Deel je vertrouwde aannemers met familie en vrienden</string>
|
||||||
|
<string name="onboarding_subscription_benefit_widgets">Widgets op het startscherm</string>
|
||||||
|
<string name="onboarding_subscription_benefit_widgets_desc">Snelle acties direct vanaf je startscherm</string>
|
||||||
|
<string name="onboarding_subscription_benefit_notifications">Actiegerichte meldingen</string>
|
||||||
|
<string name="onboarding_subscription_benefit_notifications_desc">Voltooi taken rechtstreeks vanuit meldingen</string>
|
||||||
|
<string name="onboarding_subscription_choose_plan">Kies je abonnement</string>
|
||||||
|
<string name="onboarding_subscription_monthly">Maandelijks</string>
|
||||||
|
<string name="onboarding_subscription_yearly">Jaarlijks</string>
|
||||||
|
<string name="onboarding_subscription_save">Bespaar 30%</string>
|
||||||
|
<string name="onboarding_subscription_monthly_price">$2,99/maand</string>
|
||||||
|
<string name="onboarding_subscription_yearly_price">$23,99/jaar</string>
|
||||||
|
<string name="onboarding_subscription_yearly_monthly">Slechts $1,99/maand</string>
|
||||||
|
<string name="onboarding_subscription_start_trial">Start 7-daagse gratis proefperiode</string>
|
||||||
|
<string name="onboarding_subscription_continue_free">Doorgaan met gratis versie</string>
|
||||||
|
<string name="onboarding_subscription_trial_terms">7 dagen gratis proberen, daarna %1$s. Altijd opzegbaar.</string>
|
||||||
|
<string name="onboarding_home_profile_title">Vertel ons over je woning</string>
|
||||||
|
<string name="onboarding_home_profile_subtitle">Alles optioneel — helpt ons je onderhoudsplan te personaliseren</string>
|
||||||
|
<string name="onboarding_home_profile_systems">Systemen</string>
|
||||||
|
<string name="onboarding_home_profile_features">Kenmerken</string>
|
||||||
|
<string name="onboarding_home_profile_exterior">Buitenkant</string>
|
||||||
|
<string name="onboarding_home_profile_interior">Binnenkant</string>
|
||||||
|
<string name="for_you_tab">Voor jou</string>
|
||||||
|
<string name="browse_tab">Bladeren</string>
|
||||||
|
<string name="biometric_lock_title">App vergrendeld</string>
|
||||||
|
<string name="biometric_lock_description">Verifieer om honeyDue te ontgrendelen</string>
|
||||||
|
<string name="biometric_lock_setting_title">Biometrische vergrendeling</string>
|
||||||
|
<string name="biometric_lock_setting_subtitle">Vereis verificatie bij het openen van de app</string>
|
||||||
|
<string name="biometric_prompt_title">honeyDue ontgrendelen</string>
|
||||||
|
<string name="biometric_prompt_subtitle">Bevestig je identiteit om door te gaan</string>
|
||||||
|
<string name="biometric_unlock_button">Ontgrendelen met biometrie</string>
|
||||||
|
<string name="biometric_auth_failed">Verificatie mislukt</string>
|
||||||
|
<string name="biometric_not_available">Biometrische verificatie is niet beschikbaar op dit apparaat</string>
|
||||||
|
<string name="notif_channel_task_reminder_name">Taakherinneringen</string>
|
||||||
|
<string name="notif_channel_task_reminder_description">Herinneringen voor aankomende en bijna vervallende taken</string>
|
||||||
|
<string name="notif_channel_task_overdue_name">Achterstallige taken</string>
|
||||||
|
<string name="notif_channel_task_overdue_description">Waarschuwingen wanneer een taak over de vervaldatum is</string>
|
||||||
|
<string name="notif_channel_residence_invite_name">Woninguitnodigingen</string>
|
||||||
|
<string name="notif_channel_residence_invite_description">Uitnodigingen om een gedeelde woning toe te treden</string>
|
||||||
|
<string name="notif_channel_subscription_name">Abonnementsupdates</string>
|
||||||
|
<string name="notif_channel_subscription_description">Updates over abonnementsstatus en facturering</string>
|
||||||
|
<string name="common_selected">Geselecteerd</string>
|
||||||
|
<string name="common_open">Openen</string>
|
||||||
|
<string name="common_skip">Overslaan</string>
|
||||||
|
<string name="common_or">of</string>
|
||||||
|
<string name="common_info">Info</string>
|
||||||
|
<string name="common_verified">Geverifieerd</string>
|
||||||
|
<string name="common_warning">Waarschuwing</string>
|
||||||
|
<string name="common_photo">Foto</string>
|
||||||
|
<string name="common_included">Inbegrepen</string>
|
||||||
|
<string name="common_verifying">Verifiëren…</string>
|
||||||
|
<string name="time_am">AM</string>
|
||||||
|
<string name="time_pm">PM</string>
|
||||||
|
<string name="time_eve">AVOND</string>
|
||||||
|
<string name="error_network_title">Netwerkfout</string>
|
||||||
|
<string name="image_failed_to_load">Laden mislukt</string>
|
||||||
|
<string name="theme_appearance_title">Weergave</string>
|
||||||
|
<string name="theme_use_system_colors">Systeemkleuren gebruiken</string>
|
||||||
|
<string name="theme_material_you_desc">Material You van Android 12+ volgen (achtergrondkleuren)</string>
|
||||||
|
<string name="paywall_choose_plan_title">Kies je abonnement</string>
|
||||||
|
<string name="paywall_choose_plan_subtitle">Upgrade naar Pro voor onbeperkte toegang</string>
|
||||||
|
<string name="paywall_upgrade_to_pro">Upgraden naar Pro</string>
|
||||||
|
<string name="paywall_col_feature">Functie</string>
|
||||||
|
<string name="paywall_col_free">Gratis</string>
|
||||||
|
<string name="paywall_col_pro">Pro</string>
|
||||||
|
<string name="paywall_feat_properties">Panden</string>
|
||||||
|
<string name="paywall_feat_tasks">Taken</string>
|
||||||
|
<string name="paywall_feat_contractors">Aannemers</string>
|
||||||
|
<string name="paywall_feat_documents">Documenten</string>
|
||||||
|
<string name="paywall_val_1_property">1 pand</string>
|
||||||
|
<string name="paywall_val_10_tasks">10 taken</string>
|
||||||
|
<string name="paywall_val_unlimited">Onbeperkt</string>
|
||||||
|
<string name="paywall_val_not_available">Niet beschikbaar</string>
|
||||||
|
<string name="reset_check_email_title">Controleer je e-mail</string>
|
||||||
|
<string name="reset_sent_code_to">We hebben een 6-cijferige code gestuurd naar</string>
|
||||||
|
<string name="reset_code_expires">De code verloopt over 15 minuten</string>
|
||||||
|
<string name="reset_enter_code_hint">Voer de 6-cijferige code uit je e-mail in</string>
|
||||||
|
<string name="reset_code_verified_msg">Code geverifieerd! Stel nu je nieuwe wachtwoord in</string>
|
||||||
|
<string name="reset_didnt_receive">Code niet ontvangen?</string>
|
||||||
|
<string name="reset_check_spam">Controleer je spammap als je hem niet ziet</string>
|
||||||
|
<string name="reset_verify_failed_title">Codeverificatie mislukt</string>
|
||||||
|
<string name="verify_email_required_msg">E-mailverificatie is vereist. Controleer je inbox op een 6-cijferige code.</string>
|
||||||
|
<string name="verify_email_invalid_code">Voer een geldige 6-cijferige code in</string>
|
||||||
|
<string name="verify_email_didnt_receive">Code niet ontvangen? Controleer je spammap of neem contact op met support.</string>
|
||||||
|
<string name="verify_email_failed_title">Verificatie mislukt</string>
|
||||||
|
<string name="reset_pw_success_msg">Je wachtwoord is succesvol opnieuw ingesteld</string>
|
||||||
|
<string name="reset_pw_can_login">Je kunt nu inloggen met je nieuwe wachtwoord</string>
|
||||||
|
<string name="reset_return_to_login">Terug naar inloggen</string>
|
||||||
|
<string name="reset_set_new_pw_title">Nieuw wachtwoord instellen</string>
|
||||||
|
<string name="reset_create_strong_pw">Maak een sterk wachtwoord om je account te beveiligen</string>
|
||||||
|
<string name="reset_pw_requirements">Wachtwoordvereisten</string>
|
||||||
|
<string name="reset_pw_failed_title">Opnieuw instellen van wachtwoord mislukt</string>
|
||||||
|
<string name="forgot_send_code_hint">We sturen een 6-cijferige verificatiecode naar dit adres</string>
|
||||||
|
<string name="forgot_check_email_msg">Controleer je e-mail op een 6-cijferige verificatiecode</string>
|
||||||
|
<string name="forgot_back_to_login">Wachtwoord weer te binnen? Terug naar inloggen</string>
|
||||||
|
<string name="forgot_send_failed_title">Verzenden van resetcode mislukt</string>
|
||||||
|
<string name="join_property_title">Deelnemen aan pand</string>
|
||||||
|
<string name="join_shared_property_header">Deelnemen aan een gedeeld pand</string>
|
||||||
|
<string name="join_enter_code_desc">Voer de 6-tekens deelcode in die door de eigenaar is verstrekt.</string>
|
||||||
|
<string name="join_share_code_label">Deelcode</string>
|
||||||
|
<string name="join_share_code_placeholder">ABC123</string>
|
||||||
|
<string name="join_code_helper">Codes bestaan uit 6 hoofdletters</string>
|
||||||
|
<string name="join_joining">Deelnemen…</string>
|
||||||
|
<string name="biometric_enter_pin">Voer pincode in om te ontgrendelen</string>
|
||||||
|
<string name="biometric_pin_label">4-cijferige pincode</string>
|
||||||
|
<string name="biometric_incorrect_pin">Onjuiste pincode</string>
|
||||||
|
<string name="biometric_unlock">Ontgrendelen</string>
|
||||||
|
<string name="tasks_create_failed_title">Taak aanmaken mislukt</string>
|
||||||
|
<string name="tasks_all_title">Alle taken</string>
|
||||||
|
<string name="tasks_add">Taak toevoegen</string>
|
||||||
|
<string name="tasks_load_failed_title">Taken laden mislukt</string>
|
||||||
|
<string name="tasks_all_empty_subtitle">Maak je eerste taak om te beginnen</string>
|
||||||
|
<string name="tasks_add_property_first">Voeg eerst een pand toe via het tabblad Woningen</string>
|
||||||
|
<string name="residences_upgrade_to_add">Upgraden om toe te voegen</string>
|
||||||
|
<string name="residences_primary_cd">Hoofdwoning</string>
|
||||||
|
<string name="residence_unit_label">Eenheid: %1$s</string>
|
||||||
|
<string name="residence_error_loading_tasks">Fout bij het laden van taken: %1$s</string>
|
||||||
|
<string name="residence_error_loading_contractors">Fout bij het laden van aannemers: %1$s</string>
|
||||||
|
<string name="profile_subscription_cd">Abonnement</string>
|
||||||
|
<string name="onboarding_sub_cancel_note">Op elk moment opzegbaar in Instellingen • Geen verplichting</string>
|
||||||
|
<string name="onboarding_joining_residence">Deelnemen aan woning…</string>
|
||||||
|
<string name="onboarding_sub_trial_legal">7 dagen gratis proberen, daarna %1$s</string>
|
||||||
|
<string name="common_collapse">Inklappen</string>
|
||||||
|
<string name="common_expand">Uitklappen</string>
|
||||||
|
<string name="common_not_selected">Niet geselecteerd</string>
|
||||||
|
<string name="auth_logging_in">Inloggen…</string>
|
||||||
|
<string name="auth_requirement_met">Aan vereiste voldaan</string>
|
||||||
|
<string name="auth_requirement_not_met">Niet aan vereiste voldaan</string>
|
||||||
|
<string name="error_something_wrong">Er is iets misgegaan</string>
|
||||||
|
<string name="paywall_feat_not_included">Niet inbegrepen</string>
|
||||||
|
<string name="completions_contractor_prefix">Aannemer:</string>
|
||||||
|
<string name="completions_completed_by_prefix">Voltooid door:</string>
|
||||||
|
<string name="completions_remove_photo">Foto verwijderen</string>
|
||||||
|
<string name="completions_star_rating_cd">%1$d sterren</string>
|
||||||
|
<string name="photos_completion_title">Voltooiingsfoto\'s</string>
|
||||||
|
<string name="photos_task_completion_cd">Foto van taakvoltooiing</string>
|
||||||
|
<string name="documents_residence_ref">Woning #%1$d</string>
|
||||||
|
<string name="documents_task_ref">Taak #%1$d</string>
|
||||||
|
<string name="documents_image_index">Afbeelding %1$d van %2$d</string>
|
||||||
|
<string name="documents_images_title">Documentafbeeldingen</string>
|
||||||
|
<string name="documents_empty_no_warranties">Geen garanties gevonden</string>
|
||||||
|
<string name="documents_empty_no_documents">Geen documenten gevonden</string>
|
||||||
|
<string name="documents_days_remaining_count">Nog %1$d dagen</string>
|
||||||
|
<string name="contractors_property_ref">Pand #</string>
|
||||||
|
<string name="residence_share_failed">Delen van woning mislukt</string>
|
||||||
|
<string name="theme_default">Standaard</string>
|
||||||
|
<string name="theme_default_desc">Levendige iOS-systeemkleuren</string>
|
||||||
|
<string name="theme_teal">Blauwgroen</string>
|
||||||
|
<string name="theme_teal_desc">Blauwgroen met warme accenten</string>
|
||||||
|
<string name="theme_ocean">Oceaan</string>
|
||||||
|
<string name="theme_ocean_desc">Diepe blauwtinten en koraaltinten</string>
|
||||||
|
<string name="theme_forest">Bos</string>
|
||||||
|
<string name="theme_forest_desc">Aardse groentinten en gouden tinten</string>
|
||||||
|
<string name="theme_sunset">Zonsondergang</string>
|
||||||
|
<string name="theme_sunset_desc">Warme oranje- en roodtinten</string>
|
||||||
|
<string name="theme_monochrome">Monochroom</string>
|
||||||
|
<string name="theme_monochrome_desc">Elegante grijstinten</string>
|
||||||
|
<string name="theme_lavender">Lavendel</string>
|
||||||
|
<string name="theme_lavender_desc">Zacht paars met roze accenten</string>
|
||||||
|
<string name="theme_crimson">Karmozijn</string>
|
||||||
|
<string name="theme_crimson_desc">Fel rood met warme accenten</string>
|
||||||
|
<string name="theme_midnight">Middernacht</string>
|
||||||
|
<string name="theme_midnight_desc">Diep marineblauw met hemelsblauw</string>
|
||||||
|
<string name="theme_desert">Woestijn</string>
|
||||||
|
<string name="theme_desert_desc">Warme terracotta- en zandtinten</string>
|
||||||
|
<string name="theme_mint">Mint</string>
|
||||||
|
<string name="theme_mint_desc">Fris groen met turquoise</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -472,4 +472,585 @@
|
|||||||
<string name="subscription_features">Recursos Premium</string>
|
<string name="subscription_features">Recursos Premium</string>
|
||||||
<string name="subscription_limit_properties">Voce atingiu o limite de propriedades do seu plano</string>
|
<string name="subscription_limit_properties">Voce atingiu o limite de propriedades do seu plano</string>
|
||||||
<string name="subscription_limit_tasks">Voce atingiu o limite de tarefas do seu plano</string>
|
<string name="subscription_limit_tasks">Voce atingiu o limite de tarefas do seu plano</string>
|
||||||
|
<string name="home_profile_heating">Aquecimento</string>
|
||||||
|
<string name="home_profile_cooling">Refrigeração</string>
|
||||||
|
<string name="home_profile_water_heater">Aquecedor de água</string>
|
||||||
|
<string name="home_profile_pool">Piscina</string>
|
||||||
|
<string name="home_profile_sprinkler_system">Sistema de irrigação</string>
|
||||||
|
<string name="home_profile_fireplace">Lareira</string>
|
||||||
|
<string name="home_profile_garage">Garagem</string>
|
||||||
|
<string name="home_profile_basement">Porão</string>
|
||||||
|
<string name="home_profile_attic">Sótão</string>
|
||||||
|
<string name="home_profile_septic">Fossa séptica</string>
|
||||||
|
<string name="home_profile_roof_type">Tipo de telhado</string>
|
||||||
|
<string name="home_profile_exterior">Fachada</string>
|
||||||
|
<string name="home_profile_flooring">Piso</string>
|
||||||
|
<string name="home_profile_landscaping">Paisagismo</string>
|
||||||
|
<string name="onboarding_first_task_selected_count">%1$d tarefas selecionadas</string>
|
||||||
|
<string name="onboarding_first_task_add_continue">Adicionar %1$d tarefas e continuar</string>
|
||||||
|
<string name="onboarding_first_task_finding">Buscando tarefas para sua casa...</string>
|
||||||
|
<string name="onboarding_first_task_no_suggestions">Nenhuma sugestão personalizada ainda — explore o catálogo completo ou pule esta etapa.</string>
|
||||||
|
<string name="onboarding_first_task_browse_all">Ver tudo</string>
|
||||||
|
<string name="onboarding_first_task_skip">Pular</string>
|
||||||
|
<string name="onboarding_first_task_suggestions_error">Não foi possível carregar suas sugestões</string>
|
||||||
|
<string name="onboarding_first_task_connection_error">Verifique sua conexão e tente novamente.</string>
|
||||||
|
<string name="onboarding_first_task_loading_catalog">Carregando o catálogo de tarefas...</string>
|
||||||
|
<string name="onboarding_first_task_catalog_error">Não foi possível carregar o catálogo de tarefas</string>
|
||||||
|
<string name="onboarding_first_task_no_templates">Nenhum modelo disponível no momento.</string>
|
||||||
|
<string name="onboarding_first_task_selected">Selecionada</string>
|
||||||
|
<string name="onboarding_first_task_offline">Offline</string>
|
||||||
|
<string name="onboarding_first_task_skip_for_now">Pular por enquanto</string>
|
||||||
|
<string name="upgrade_hero_title">Fazer upgrade para o Pro</string>
|
||||||
|
<string name="upgrade_hero_subtitle">Desbloqueie todo o potencial do honeyDue</string>
|
||||||
|
<string name="upgrade_choose_plan">Escolha seu plano</string>
|
||||||
|
<string name="upgrade_plan_yearly">Anual</string>
|
||||||
|
<string name="upgrade_plan_monthly">Mensal</string>
|
||||||
|
<string name="upgrade_plan_save_50">Economize 50%</string>
|
||||||
|
<string name="upgrade_best_value">MELHOR OFERTA</string>
|
||||||
|
<string name="upgrade_whats_included">O que está incluído</string>
|
||||||
|
<string name="upgrade_feature_unlimited_properties">Imóveis ilimitados</string>
|
||||||
|
<string name="upgrade_feature_unlimited_properties_desc">Acompanhe a manutenção de todas as suas casas</string>
|
||||||
|
<string name="upgrade_feature_unlimited_tasks">Tarefas ilimitadas</string>
|
||||||
|
<string name="upgrade_feature_unlimited_tasks_desc">Nunca mais esqueça uma tarefa de manutenção</string>
|
||||||
|
<string name="upgrade_feature_contractor_management">Gestão de prestadores</string>
|
||||||
|
<string name="upgrade_feature_contractor_management_desc">Salve e avalie seus prestadores de confiança</string>
|
||||||
|
<string name="upgrade_feature_document_vault">Cofre de documentos</string>
|
||||||
|
<string name="upgrade_feature_document_vault_desc">Guarde garantias, recibos e manuais</string>
|
||||||
|
<string name="upgrade_feature_family_sharing">Compartilhamento familiar</string>
|
||||||
|
<string name="upgrade_feature_family_sharing_desc">Convide familiares para colaborar</string>
|
||||||
|
<string name="upgrade_feature_smart_reminders">Lembretes inteligentes</string>
|
||||||
|
<string name="upgrade_feature_smart_reminders_desc">Seja avisado quando as tarefas vencerem</string>
|
||||||
|
<string name="upgrade_feature_document_warranty_storage">Armazenamento de documentos e garantias</string>
|
||||||
|
<string name="upgrade_subscribe_now">Assinar agora</string>
|
||||||
|
<string name="upgrade_restore_purchases">Restaurar compras</string>
|
||||||
|
<string name="upgrade_terms_text">A assinatura é renovada automaticamente, a menos que seja cancelada pelo menos 24 horas antes do fim do período atual. Gerencie as assinaturas nas configurações do seu dispositivo.</string>
|
||||||
|
<string name="upgrade_terms_of_use">Termos de uso</string>
|
||||||
|
<string name="upgrade_included">Incluído</string>
|
||||||
|
<string name="upgrade_compare_free_vs_pro">Comparar Grátis vs Pro</string>
|
||||||
|
<string name="upgrade_maybe_later">Talvez depois</string>
|
||||||
|
<string name="upgrade_warning">Aviso</string>
|
||||||
|
<string name="upgrade_subscription_active">Assinatura ativa</string>
|
||||||
|
<string name="upgrade_subscription_active_message">Agora você tem acesso total a todos os recursos Pro!</string>
|
||||||
|
<string name="upgrade_feature_required_title">Upgrade necessário</string>
|
||||||
|
<string name="upgrade_feature_required_message">Este recurso está disponível com a assinatura Pro.</string>
|
||||||
|
<string name="upgrade_prompt_default_message">Desbloqueie acesso ilimitado a todos os recursos</string>
|
||||||
|
<string name="upgrade_product_monthly_name">honeyDue Pro Mensal</string>
|
||||||
|
<string name="upgrade_product_annual_name">honeyDue Pro Anual</string>
|
||||||
|
<string name="upgrade_billed_monthly">Cobrado mensalmente</string>
|
||||||
|
<string name="upgrade_billed_annually">Cobrado anualmente</string>
|
||||||
|
<string name="upgrade_save_22">Economize 22%</string>
|
||||||
|
<string name="tasks_column_done">Concluídas</string>
|
||||||
|
<string name="tasks_column_archived">Arquivadas</string>
|
||||||
|
<string name="tasks_column_empty">Nenhuma tarefa</string>
|
||||||
|
<string name="tasks_new_title">Nova tarefa</string>
|
||||||
|
<string name="tasks_title_field_label">Título</string>
|
||||||
|
<string name="tasks_title_placeholder">ex.: Limpar o aquecedor de água</string>
|
||||||
|
<string name="tasks_description_placeholder">Detalhes opcionais</string>
|
||||||
|
<string name="tasks_due_date_optional_label">Data de vencimento (opcional)</string>
|
||||||
|
<string name="tasks_due_date_placeholder_format">aaaa-MM-dd</string>
|
||||||
|
<string name="tasks_due_date_blank_helper">Deixe em branco para sem data de vencimento</string>
|
||||||
|
<string name="tasks_estimated_cost_optional_label">Custo estimado (opcional)</string>
|
||||||
|
<string name="suggestions_title">Tarefas sugeridas</string>
|
||||||
|
<string name="suggestions_skip">Pular</string>
|
||||||
|
<string name="suggestions_accept">Aceitar</string>
|
||||||
|
<string name="suggestions_load_failed">Não foi possível carregar as sugestões</string>
|
||||||
|
<string name="suggestions_empty_title">Nenhuma sugestão ainda</string>
|
||||||
|
<string name="suggestions_empty_subtitle">Complete o perfil da sua casa para ver recomendações personalizadas.</string>
|
||||||
|
<string name="completion_history_title">Histórico de conclusões</string>
|
||||||
|
<string name="completion_history_count_one">%1$d conclusão</string>
|
||||||
|
<string name="completion_history_count_other">%1$d conclusões</string>
|
||||||
|
<string name="completion_history_loading">Carregando conclusões...</string>
|
||||||
|
<string name="completion_history_load_failed">Falha ao carregar as conclusões</string>
|
||||||
|
<string name="completion_history_empty_title">Nenhuma conclusão ainda</string>
|
||||||
|
<string name="completion_history_empty_message">Esta tarefa não foi concluída.</string>
|
||||||
|
<string name="completion_history_completed_by">Concluída por %1$s</string>
|
||||||
|
<string name="completion_history_view_photo">Ver foto</string>
|
||||||
|
<string name="manage_users_remove_user">Remover usuário</string>
|
||||||
|
<string name="manage_users_copy_code">Copiar código</string>
|
||||||
|
<string name="manage_users_code_copied">Código copiado para a área de transferência</string>
|
||||||
|
<string name="manage_users_load_failed">Não foi possível carregar os usuários</string>
|
||||||
|
<string name="manage_users_remove_confirm">Remover %1$s deste imóvel?</string>
|
||||||
|
<string name="properties_shared_users_count">Usuários compartilhados (%1$d)</string>
|
||||||
|
<string name="properties_no_shared_users">Nenhum usuário compartilhado</string>
|
||||||
|
<string name="properties_shared_users_helper">Usuários com acesso a este imóvel. Use o botão de compartilhar para convidar outras pessoas.</string>
|
||||||
|
<string name="properties_remove_user_confirm">Tem certeza de que deseja remover %1$s deste imóvel?</string>
|
||||||
|
<string name="properties_remove_button">Remover</string>
|
||||||
|
<string name="documents_expires_label">Vence em</string>
|
||||||
|
<string name="auth_password_requirement_length">Pelo menos 8 caracteres</string>
|
||||||
|
<string name="auth_password_requirement_uppercase">Contém uma letra maiúscula</string>
|
||||||
|
<string name="auth_password_requirement_lowercase">Contém uma letra minúscula</string>
|
||||||
|
<string name="auth_password_requirement_digit">Contém um número</string>
|
||||||
|
<string name="auth_password_requirement_match">As senhas coincidem</string>
|
||||||
|
<string name="auth_password_requirements_title">Requisitos da senha</string>
|
||||||
|
<string name="auth_password_complexity_error">A senha deve ter pelo menos 8 caracteres, com no mínimo uma letra maiúscula, uma minúscula e um número</string>
|
||||||
|
<string name="properties_join_residence_title">Entrar na residência</string>
|
||||||
|
<string name="properties_join_residence_message">Deseja entrar nesta residência compartilhada?</string>
|
||||||
|
<string name="properties_join_success">Você entrou na residência</string>
|
||||||
|
<string name="properties_join_success_message">Agora você tem acesso a %1$s.</string>
|
||||||
|
<string name="properties_join_failed">Falha ao entrar</string>
|
||||||
|
<string name="properties_joining">Entrando...</string>
|
||||||
|
<string name="properties_shared_by">Compartilhado por: %1$s</string>
|
||||||
|
<string name="properties_expires">Expira em: %1$s</string>
|
||||||
|
<string name="properties_share_upgrade_title">Recurso Pro</string>
|
||||||
|
<string name="properties_share_upgrade_message">Compartilhar residências é um recurso Pro. Faça upgrade para convidar familiares a colaborar na manutenção da casa.</string>
|
||||||
|
<string name="tasks_failed_to_cancel">Falha ao cancelar a tarefa</string>
|
||||||
|
<string name="tasks_failed_to_restore">Falha ao restaurar a tarefa</string>
|
||||||
|
<string name="tasks_failed_to_mark_in_progress">Falha ao marcar tarefa em andamento</string>
|
||||||
|
<string name="tasks_failed_to_archive">Falha ao arquivar a tarefa</string>
|
||||||
|
<string name="tasks_failed_to_unarchive">Falha ao desarquivar a tarefa</string>
|
||||||
|
<string name="tasks_card_in_progress">EM ANDAMENTO</string>
|
||||||
|
<string name="tasks_card_actions">Ações</string>
|
||||||
|
<string name="tasks_card_mark_in_progress">Marcar em andamento</string>
|
||||||
|
<string name="tasks_card_complete_task">Concluir tarefa</string>
|
||||||
|
<string name="tasks_card_edit_task">Editar tarefa</string>
|
||||||
|
<string name="tasks_card_cancel_task">Cancelar tarefa</string>
|
||||||
|
<string name="tasks_card_restore_task">Restaurar tarefa</string>
|
||||||
|
<string name="tasks_card_archive_task">Arquivar tarefa</string>
|
||||||
|
<string name="tasks_card_unarchive_task">Desarquivar tarefa</string>
|
||||||
|
<string name="tasks_card_not_available">N/D</string>
|
||||||
|
<string name="tasks_card_completed_by">Por: %1$s</string>
|
||||||
|
<string name="tasks_card_cost">Custo: R$%1$s</string>
|
||||||
|
<string name="tasks_card_view_photos">Ver fotos (%1$d)</string>
|
||||||
|
<string name="tasks_add_new">Adicionar nova tarefa</string>
|
||||||
|
<string name="tasks_property_required">Propriedade *</string>
|
||||||
|
<string name="tasks_property_error">A propriedade é obrigatória</string>
|
||||||
|
<string name="tasks_browse_templates">Explorar modelos de tarefas</string>
|
||||||
|
<string name="tasks_common_tasks">%1$d tarefas comuns</string>
|
||||||
|
<string name="tasks_category_error">A categoria é obrigatória</string>
|
||||||
|
<string name="tasks_interval_days">Intervalo em dias</string>
|
||||||
|
<string name="tasks_interval_override">Substituir o intervalo de frequência padrão</string>
|
||||||
|
<string name="tasks_custom_interval_help">Número de dias entre cada ocorrência</string>
|
||||||
|
<string name="tasks_due_date_format_error">A data de vencimento é obrigatória (formato: AAAA-MM-DD)</string>
|
||||||
|
<string name="tasks_due_date_format">Formato: AAAA-MM-DD</string>
|
||||||
|
<string name="tasks_create">Criar tarefa</string>
|
||||||
|
<string name="tasks_in_progress_label">Em andamento</string>
|
||||||
|
<string name="templates_title">Modelos de tarefas</string>
|
||||||
|
<string name="templates_done">Concluído</string>
|
||||||
|
<string name="templates_search_placeholder">Buscar modelos...</string>
|
||||||
|
<string name="templates_clear">Limpar</string>
|
||||||
|
<string name="templates_result">resultado</string>
|
||||||
|
<string name="templates_results">resultados</string>
|
||||||
|
<string name="templates_no_results_title">Nenhum modelo encontrado</string>
|
||||||
|
<string name="templates_no_results_message">Tente outro termo de busca</string>
|
||||||
|
<string name="templates_empty_title">Nenhum modelo disponível</string>
|
||||||
|
<string name="templates_empty_message">Os modelos aparecerão aqui após o carregamento</string>
|
||||||
|
<string name="templates_expand">Expandir</string>
|
||||||
|
<string name="templates_collapse">Recolher</string>
|
||||||
|
<string name="templates_add">Adicionar</string>
|
||||||
|
<string name="templates_all_categories">Todas</string>
|
||||||
|
<string name="templates_apply">Aplicar</string>
|
||||||
|
<string name="templates_apply_count">Aplicar (%1$d)</string>
|
||||||
|
<string name="templates_selected_count">%1$d selecionado(s)</string>
|
||||||
|
<string name="templates_retry">Tentar novamente</string>
|
||||||
|
<string name="templates_load_failed">Falha ao carregar os modelos</string>
|
||||||
|
<string name="templates_create_failed">Falha ao criar as tarefas</string>
|
||||||
|
<string name="completions_complete_task_title">Concluir tarefa: %1$s</string>
|
||||||
|
<string name="completions_select_contractor">Selecionar prestador (opcional)</string>
|
||||||
|
<string name="completions_choose_contractor_placeholder">Escolha um prestador ou deixe em branco</string>
|
||||||
|
<string name="completions_expand">Expandir</string>
|
||||||
|
<string name="completions_none_manual">Nenhum (entrada manual)</string>
|
||||||
|
<string name="completions_loading_contractors">Carregando prestadores...</string>
|
||||||
|
<string name="completions_error_loading_contractors">Erro ao carregar prestadores</string>
|
||||||
|
<string name="completions_completed_by_name">Concluído por (opcional)</string>
|
||||||
|
<string name="completions_completed_by_placeholder">Informe o nome se não usar o prestador acima</string>
|
||||||
|
<string name="completions_actual_cost_optional">Custo real (opcional)</string>
|
||||||
|
<string name="completions_notes_optional">Observações (opcional)</string>
|
||||||
|
<string name="completions_rating">Avaliação: %1$d de 5</string>
|
||||||
|
<string name="completions_add_images">Adicionar imagens</string>
|
||||||
|
<string name="completions_take_photo">Tirar foto</string>
|
||||||
|
<string name="completions_choose_from_library">Escolher da biblioteca</string>
|
||||||
|
<string name="completions_images_selected">%1$d imagem(ns) selecionada(s)</string>
|
||||||
|
<string name="completions_remove_image">Remover imagem</string>
|
||||||
|
<string name="completions_complete_button">Concluir</string>
|
||||||
|
<string name="completions_quality_rating">Avaliação de qualidade</string>
|
||||||
|
<string name="completions_photos_count">Fotos (%1$d/%2$d)</string>
|
||||||
|
<string name="completions_camera">Câmera</string>
|
||||||
|
<string name="completions_library">Biblioteca</string>
|
||||||
|
<string name="completions_add_photos_helper">Adicione fotos do trabalho concluído (opcional)</string>
|
||||||
|
<string name="completions_contractor_helper">Vincule esta conclusão de tarefa a um prestador</string>
|
||||||
|
<string name="completions_details_section">Detalhes da conclusão</string>
|
||||||
|
<string name="completions_optional_info">Todos os campos são opcionais</string>
|
||||||
|
<string name="completions_notes_helper">Adicione observações sobre o trabalho concluído</string>
|
||||||
|
<string name="completions_notes_placeholder">Descreva o trabalho feito, problemas encontrados, etc.</string>
|
||||||
|
<string name="completions_rate_quality">Avalie a qualidade do trabalho realizado</string>
|
||||||
|
<string name="completions_enter_manually">Informe o nome manualmente abaixo</string>
|
||||||
|
<string name="manage_users_title">Gerenciar usuários</string>
|
||||||
|
<string name="manage_users_invite_title">Convidar outras pessoas</string>
|
||||||
|
<string name="manage_users_easy_share">Compartilhamento fácil</string>
|
||||||
|
<string name="manage_users_send_invite">Enviar link de convite</string>
|
||||||
|
<string name="manage_users_easy_share_desc">Envie um arquivo .honeydue por mensagem, e-mail ou compartilhamento. Basta tocar para entrar.</string>
|
||||||
|
<string name="manage_users_share_code">Código de compartilhamento</string>
|
||||||
|
<string name="manage_users_no_code">Nenhum código ativo</string>
|
||||||
|
<string name="manage_users_generate">Gerar código</string>
|
||||||
|
<string name="manage_users_generate_new">Gerar novo código</string>
|
||||||
|
<string name="manage_users_code_desc">Compartilhe este código de 6 caracteres. A pessoa pode inseri-lo no app para entrar.</string>
|
||||||
|
<string name="manage_users_users_count">Usuários (%1$d)</string>
|
||||||
|
<string name="manage_users_owner_badge">Proprietário</string>
|
||||||
|
<string name="manage_users_remove">Remover</string>
|
||||||
|
<string name="manage_users_or">ou</string>
|
||||||
|
<string name="contractors_share">Compartilhar prestador</string>
|
||||||
|
<string name="contractors_share_upgrade_title">Recurso Pro</string>
|
||||||
|
<string name="contractors_share_upgrade_message">Compartilhar prestadores é um recurso Pro. Faça upgrade para compartilhar seus prestadores de confiança com amigos e familiares.</string>
|
||||||
|
<string name="contractors_import_title">Importar prestador</string>
|
||||||
|
<string name="contractors_import_message">Deseja importar este prestador?</string>
|
||||||
|
<string name="contractors_import_success">Prestador importado</string>
|
||||||
|
<string name="contractors_import_success_message">%1$s foi adicionado aos seus contatos.</string>
|
||||||
|
<string name="contractors_import_failed">Falha na importação</string>
|
||||||
|
<string name="contractors_shared_by">Compartilhado por: %1$s</string>
|
||||||
|
<string name="contractors_form_add_title">Adicionar prestador</string>
|
||||||
|
<string name="contractors_form_edit_title">Editar prestador</string>
|
||||||
|
<string name="contractors_form_basic_info">Informações básicas</string>
|
||||||
|
<string name="contractors_form_name_required">Nome *</string>
|
||||||
|
<string name="contractors_form_company">Empresa</string>
|
||||||
|
<string name="contractors_form_residence_optional">Residência (opcional)</string>
|
||||||
|
<string name="contractors_form_personal_no_residence">Pessoal (sem residência)</string>
|
||||||
|
<string name="contractors_form_personal_visibility">Somente você verá este prestador</string>
|
||||||
|
<string name="contractors_form_shared_visibility">Todos os usuários de %1$s verão este prestador</string>
|
||||||
|
<string name="contractors_form_contact_info">Informações de contato</string>
|
||||||
|
<string name="contractors_form_phone">Telefone</string>
|
||||||
|
<string name="contractors_form_email">E-mail</string>
|
||||||
|
<string name="contractors_form_website">Site</string>
|
||||||
|
<string name="contractors_form_specialties">Especialidades</string>
|
||||||
|
<string name="contractors_form_address_section">Endereço</string>
|
||||||
|
<string name="contractors_form_street_address">Logradouro</string>
|
||||||
|
<string name="contractors_form_city">Cidade</string>
|
||||||
|
<string name="contractors_form_state">Estado</string>
|
||||||
|
<string name="contractors_form_zip_code">CEP</string>
|
||||||
|
<string name="contractors_form_notes_section">Observações</string>
|
||||||
|
<string name="contractors_form_private_notes">Observações privadas</string>
|
||||||
|
<string name="contractors_form_mark_favorite">Marcar como favorito</string>
|
||||||
|
<string name="contractors_form_add_button">Adicionar</string>
|
||||||
|
<string name="contractors_form_save_button">Salvar</string>
|
||||||
|
<string name="documents_form_edit_warranty">Editar garantia</string>
|
||||||
|
<string name="documents_form_edit_document">Editar documento</string>
|
||||||
|
<string name="documents_form_add_warranty">Adicionar garantia</string>
|
||||||
|
<string name="documents_form_add_document">Adicionar documento</string>
|
||||||
|
<string name="documents_form_select_residence">Selecionar residência</string>
|
||||||
|
<string name="documents_form_residence_required">Residência *</string>
|
||||||
|
<string name="documents_form_document_type_required">Tipo de documento *</string>
|
||||||
|
<string name="documents_form_title_required">Título *</string>
|
||||||
|
<string name="documents_form_item_name_required">Nome do item *</string>
|
||||||
|
<string name="documents_form_model_number">Número do modelo</string>
|
||||||
|
<string name="documents_form_serial_number">Número de série</string>
|
||||||
|
<string name="documents_form_provider_required">Fornecedor/Empresa *</string>
|
||||||
|
<string name="documents_form_provider_contact">Contato do fornecedor</string>
|
||||||
|
<string name="documents_form_claim_phone">Telefone para acionamento</string>
|
||||||
|
<string name="documents_form_claim_email">E-mail para acionamento</string>
|
||||||
|
<string name="documents_form_claim_website">Site para acionamento</string>
|
||||||
|
<string name="documents_form_purchase_date">Data da compra (AAAA-MM-DD)</string>
|
||||||
|
<string name="documents_form_warranty_start">Início da garantia (AAAA-MM-DD)</string>
|
||||||
|
<string name="documents_form_warranty_end_required">Fim da garantia (AAAA-MM-DD) *</string>
|
||||||
|
<string name="documents_form_description">Descrição</string>
|
||||||
|
<string name="documents_form_category">Categoria</string>
|
||||||
|
<string name="documents_form_select_category">Selecionar categoria</string>
|
||||||
|
<string name="documents_form_category_none">Nenhuma</string>
|
||||||
|
<string name="documents_form_tags">Etiquetas</string>
|
||||||
|
<string name="documents_form_tags_placeholder">etiqueta1, etiqueta2, etiqueta3</string>
|
||||||
|
<string name="documents_form_notes">Observações</string>
|
||||||
|
<string name="documents_form_active">Ativo</string>
|
||||||
|
<string name="documents_form_existing_photos">Fotos existentes (%1$d)</string>
|
||||||
|
<string name="documents_form_new_photos">Novas fotos (%1$d/%2$d)</string>
|
||||||
|
<string name="documents_form_photos">Fotos (%1$d/%2$d)</string>
|
||||||
|
<string name="documents_form_camera">Câmera</string>
|
||||||
|
<string name="documents_form_gallery">Galeria</string>
|
||||||
|
<string name="documents_form_image_number">Imagem %1$d</string>
|
||||||
|
<string name="documents_form_remove_image">Remover imagem</string>
|
||||||
|
<string name="documents_form_update_warranty">Atualizar garantia</string>
|
||||||
|
<string name="documents_form_update_document">Atualizar documento</string>
|
||||||
|
<string name="documents_form_select_residence_error">Selecione uma residência</string>
|
||||||
|
<string name="documents_form_title_error">O título é obrigatório</string>
|
||||||
|
<string name="documents_form_item_name_error">O nome do item é obrigatório para garantias</string>
|
||||||
|
<string name="documents_form_provider_error">O fornecedor é obrigatório para garantias</string>
|
||||||
|
<string name="documents_form_date_placeholder">2024-01-15</string>
|
||||||
|
<string name="documents_form_date_placeholder_end">2025-01-15</string>
|
||||||
|
<string name="documents_form_failed_to_load_residences">Falha ao carregar residências: %1$s</string>
|
||||||
|
<string name="profile_support">Suporte</string>
|
||||||
|
<string name="profile_contact_support">Falar com o suporte</string>
|
||||||
|
<string name="profile_contact_support_subtitle">Obtenha ajuda com sua conta</string>
|
||||||
|
<string name="profile_upgrade_benefits_title">Desbloqueie recursos premium</string>
|
||||||
|
<string name="profile_upgrade_benefits_subtitle">Faça upgrade para o Pro e tenha a experiência completa</string>
|
||||||
|
<string name="profile_benefit_unlimited_properties">Propriedades ilimitadas</string>
|
||||||
|
<string name="profile_benefit_document_vault">Armazenamento de documentos e garantias</string>
|
||||||
|
<string name="profile_benefit_residence_sharing">Compartilhamento de residência</string>
|
||||||
|
<string name="profile_benefit_contractor_sharing">Compartilhamento de prestadores</string>
|
||||||
|
<string name="profile_benefit_actionable_notifications">Notificações com ações</string>
|
||||||
|
<string name="profile_benefit_widgets">Widgets na tela inicial</string>
|
||||||
|
<string name="profile_privacy">Política de privacidade</string>
|
||||||
|
<string name="profile_privacy_subtitle">Ver nossa política de privacidade</string>
|
||||||
|
<string name="profile_app_version">Versão %1$s</string>
|
||||||
|
<string name="profile_app_name">honeyDue</string>
|
||||||
|
<string name="profile_edit_profile">Editar perfil</string>
|
||||||
|
<string name="delete_account_title">Excluir conta</string>
|
||||||
|
<string name="delete_account_subtitle">Excluir sua conta permanentemente</string>
|
||||||
|
<string name="delete_account_warning">Esta ação é permanente e não pode ser desfeita. Todos os seus dados serão excluídos.</string>
|
||||||
|
<string name="delete_account_shared_warning">Quaisquer residências de sua propriedade que estejam compartilhadas com outros usuários também serão excluídas.</string>
|
||||||
|
<string name="delete_account_confirm_password">Digite sua senha para confirmar</string>
|
||||||
|
<string name="delete_account_confirm_type">Digite EXCLUIR para confirmar</string>
|
||||||
|
<string name="delete_account_button">Excluir minha conta</string>
|
||||||
|
<string name="delete_account_cancel">Cancelar</string>
|
||||||
|
<string name="delete_account_success">Conta excluída com sucesso</string>
|
||||||
|
<string name="delete_account_failed">Falha ao excluir a conta</string>
|
||||||
|
<string name="notifications_daily_digest">Resumo diário</string>
|
||||||
|
<string name="notifications_daily_digest_desc">Visão geral diária das tarefas a vencer e vencidas</string>
|
||||||
|
<string name="notifications_email_section">Notificações por e-mail</string>
|
||||||
|
<string name="notifications_email_task_completed">E-mail de tarefa concluída</string>
|
||||||
|
<string name="notifications_email_task_completed_desc">Receba um e-mail quando uma tarefa for concluída</string>
|
||||||
|
<string name="notifications_set_custom_time">Definir horário personalizado</string>
|
||||||
|
<string name="notifications_change_time">Alterar</string>
|
||||||
|
<string name="notifications_select_time">Selecionar horário da notificação</string>
|
||||||
|
<string name="notifications_master_title">Todas as notificações</string>
|
||||||
|
<string name="notifications_master_desc">Ative ou desative todas as categorias com um toque</string>
|
||||||
|
<string name="notifications_categories_section">Categorias</string>
|
||||||
|
<string name="notifications_category_task_reminder">Lembretes de tarefas</string>
|
||||||
|
<string name="notifications_category_task_reminder_desc">Lembretes de tarefas próximas e a vencer</string>
|
||||||
|
<string name="notifications_category_task_overdue">Tarefas vencidas</string>
|
||||||
|
<string name="notifications_category_task_overdue_desc">Alertas quando uma tarefa passa do prazo</string>
|
||||||
|
<string name="notifications_category_residence_invite">Convites para residência</string>
|
||||||
|
<string name="notifications_category_residence_invite_desc">Convites para entrar em uma residência compartilhada</string>
|
||||||
|
<string name="notifications_category_subscription">Atualizações de assinatura</string>
|
||||||
|
<string name="notifications_category_subscription_desc">Mudanças de cobrança e status do plano</string>
|
||||||
|
<string name="notifications_open_system_settings">Abrir configurações do sistema</string>
|
||||||
|
<string name="notifications_system_settings_desc">Ajuste sons, selos e o comportamento do Não Perturbe nas configurações do Android</string>
|
||||||
|
<string name="common_share">Compartilhar</string>
|
||||||
|
<string name="common_import">Importar</string>
|
||||||
|
<string name="common_importing">Importando...</string>
|
||||||
|
<string name="common_try_again">Tentar novamente</string>
|
||||||
|
<string name="home_overdue">Vencidas</string>
|
||||||
|
<string name="home_due_this_week">A vencer esta semana</string>
|
||||||
|
<string name="home_next_30_days">Próximos 30 dias</string>
|
||||||
|
<string name="home_your_properties">Suas propriedades</string>
|
||||||
|
<string name="onboarding_welcome_title">Bem-vindo ao honeyDue</string>
|
||||||
|
<string name="onboarding_welcome_subtitle">Seu companheiro na manutenção da casa</string>
|
||||||
|
<string name="onboarding_start_fresh">Começar do zero</string>
|
||||||
|
<string name="onboarding_join_existing">Entrar em uma casa existente</string>
|
||||||
|
<string name="onboarding_already_have_account">Já tem uma conta? Entrar</string>
|
||||||
|
<string name="onboarding_skip">Pular</string>
|
||||||
|
<string name="onboarding_continue">Continuar</string>
|
||||||
|
<string name="onboarding_get_started">Começar</string>
|
||||||
|
<string name="onboarding_feature_tasks_title">Nunca esqueça uma tarefa</string>
|
||||||
|
<string name="onboarding_feature_tasks_desc">Acompanhe todas as tarefas de manutenção da casa em um só lugar, com lembretes inteligentes</string>
|
||||||
|
<string name="onboarding_feature_docs_title">Documentos ao seu alcance</string>
|
||||||
|
<string name="onboarding_feature_docs_desc">Guarde garantias, manuais e recibos com segurança e acesse a qualquer momento</string>
|
||||||
|
<string name="onboarding_feature_contractors_title">Seus prestadores de confiança</string>
|
||||||
|
<string name="onboarding_feature_contractors_desc">Mantenha todos os contatos de prestadores organizados e de fácil acesso</string>
|
||||||
|
<string name="onboarding_feature_family_title">Compartilhe com a família</string>
|
||||||
|
<string name="onboarding_feature_family_desc">Convide familiares para colaborar juntos na manutenção da casa</string>
|
||||||
|
<string name="onboarding_feature_notifications_title">Notificações inteligentes</string>
|
||||||
|
<string name="onboarding_feature_notifications_desc">Receba lembretes com ações que permitem concluir tarefas direto pela notificação</string>
|
||||||
|
<string name="onboarding_feature_widgets_title">Widgets na tela inicial</string>
|
||||||
|
<string name="onboarding_feature_widgets_desc">Acesso rápido a tarefas e lembretes direto da sua tela inicial</string>
|
||||||
|
<string name="onboarding_location_title">Onde fica a sua casa?</string>
|
||||||
|
<string name="onboarding_location_subtitle">Vamos sugerir tarefas de manutenção específicas para o clima da sua região</string>
|
||||||
|
<string name="onboarding_location_use_my_location">Usar minha localização</string>
|
||||||
|
<string name="onboarding_location_detecting">Detectando...</string>
|
||||||
|
<string name="onboarding_location_enter_zip">Inserir o CEP</string>
|
||||||
|
<string name="onboarding_location_enter_zip_prompt">Digite seu CEP</string>
|
||||||
|
<string name="onboarding_location_zip_placeholder">CEP</string>
|
||||||
|
<string name="onboarding_name_residence_title">Dê um nome à sua casa</string>
|
||||||
|
<string name="onboarding_name_residence_subtitle">Dê um nome à sua propriedade para ajudar a identificá-la</string>
|
||||||
|
<string name="onboarding_name_residence_placeholder">ex.: Minha casa, Casa de praia, Apartamento</string>
|
||||||
|
<string name="onboarding_name_residence_hint">Você pode adicionar mais detalhes depois</string>
|
||||||
|
<string name="onboarding_create_account_title">Salve sua casa</string>
|
||||||
|
<string name="onboarding_create_account_subtitle">Crie uma conta para sincronizar entre dispositivos</string>
|
||||||
|
<string name="onboarding_create_with_email">Criar conta com e-mail</string>
|
||||||
|
<string name="onboarding_verify_email_title">Verifique seu e-mail</string>
|
||||||
|
<string name="onboarding_verify_email_subtitle">Enviamos um código de 6 dígitos para seu e-mail. Digite-o abaixo para verificar sua conta.</string>
|
||||||
|
<string name="onboarding_verify_email_hint">Não recebeu o código? Verifique sua caixa de spam</string>
|
||||||
|
<string name="onboarding_join_title">Entrar em uma residência</string>
|
||||||
|
<string name="onboarding_join_subtitle">Digite o código de 6 caracteres compartilhado com você para entrar em uma casa existente</string>
|
||||||
|
<string name="onboarding_join_placeholder">Digite o código de compartilhamento</string>
|
||||||
|
<string name="onboarding_join_button">Entrar na residência</string>
|
||||||
|
<string name="onboarding_tasks_title">Tudo pronto!</string>
|
||||||
|
<string name="onboarding_tasks_subtitle">Vamos começar com algumas tarefas. Quanto mais você escolher, mais ajudaremos você a lembrar!</string>
|
||||||
|
<string name="onboarding_tasks_selected">%1$d/%2$d tarefas selecionadas</string>
|
||||||
|
<string name="onboarding_tasks_add_popular">Adicionar as mais populares</string>
|
||||||
|
<string name="onboarding_tasks_continue">Adicionar %1$d tarefas e continuar</string>
|
||||||
|
<string name="onboarding_tasks_skip">Pular por enquanto</string>
|
||||||
|
<string name="onboarding_category_hvac">Climatização e HVAC</string>
|
||||||
|
<string name="onboarding_category_safety">Segurança e proteção</string>
|
||||||
|
<string name="onboarding_category_plumbing">Encanamento</string>
|
||||||
|
<string name="onboarding_category_outdoor">Área externa e jardim</string>
|
||||||
|
<string name="onboarding_category_appliances">Eletrodomésticos</string>
|
||||||
|
<string name="onboarding_category_general">Casa em geral</string>
|
||||||
|
<string name="onboarding_subscription_title">Seja Pro</string>
|
||||||
|
<string name="onboarding_subscription_subtitle">Leve a gestão da sua casa ao próximo nível</string>
|
||||||
|
<string name="onboarding_subscription_pro">HONEYDUE PRO</string>
|
||||||
|
<string name="onboarding_subscription_social_proof">4.9 • Mais de 10 mil donos de casa</string>
|
||||||
|
<string name="onboarding_subscription_benefit_properties">Propriedades ilimitadas</string>
|
||||||
|
<string name="onboarding_subscription_benefit_properties_desc">Acompanhe todas as casas que você possui</string>
|
||||||
|
<string name="onboarding_subscription_benefit_reminders">Lembretes inteligentes</string>
|
||||||
|
<string name="onboarding_subscription_benefit_reminders_desc">Nunca perca um prazo de manutenção</string>
|
||||||
|
<string name="onboarding_subscription_benefit_documents">Cofre de documentos</string>
|
||||||
|
<string name="onboarding_subscription_benefit_documents_desc">Todos os seus documentos em um só lugar</string>
|
||||||
|
<string name="onboarding_subscription_benefit_family">Compartilhamento familiar</string>
|
||||||
|
<string name="onboarding_subscription_benefit_family_desc">Deixe todos na mesma página</string>
|
||||||
|
<string name="onboarding_subscription_benefit_insights">Análise de gastos</string>
|
||||||
|
<string name="onboarding_subscription_benefit_insights_desc">Veja para onde vai o seu dinheiro</string>
|
||||||
|
<string name="onboarding_subscription_benefit_contractor_sharing">Compartilhamento de prestadores</string>
|
||||||
|
<string name="onboarding_subscription_benefit_contractor_sharing_desc">Compartilhe seus prestadores de confiança com a família e amigos</string>
|
||||||
|
<string name="onboarding_subscription_benefit_widgets">Widgets na tela inicial</string>
|
||||||
|
<string name="onboarding_subscription_benefit_widgets_desc">Ações rápidas direto da sua tela inicial</string>
|
||||||
|
<string name="onboarding_subscription_benefit_notifications">Notificações com ações</string>
|
||||||
|
<string name="onboarding_subscription_benefit_notifications_desc">Conclua tarefas direto pelas notificações</string>
|
||||||
|
<string name="onboarding_subscription_choose_plan">Escolha seu plano</string>
|
||||||
|
<string name="onboarding_subscription_monthly">Mensal</string>
|
||||||
|
<string name="onboarding_subscription_yearly">Anual</string>
|
||||||
|
<string name="onboarding_subscription_save">Economize 30%</string>
|
||||||
|
<string name="onboarding_subscription_monthly_price">R$2,99/mês</string>
|
||||||
|
<string name="onboarding_subscription_yearly_price">R$23,99/ano</string>
|
||||||
|
<string name="onboarding_subscription_yearly_monthly">Apenas R$1,99/mês</string>
|
||||||
|
<string name="onboarding_subscription_start_trial">Iniciar teste grátis de 7 dias</string>
|
||||||
|
<string name="onboarding_subscription_continue_free">Continuar com o plano gratuito</string>
|
||||||
|
<string name="onboarding_subscription_trial_terms">Teste grátis de 7 dias, depois %1$s. Cancele quando quiser.</string>
|
||||||
|
<string name="onboarding_home_profile_title">Conte-nos sobre sua casa</string>
|
||||||
|
<string name="onboarding_home_profile_subtitle">Tudo opcional — ajuda a personalizar seu plano de manutenção</string>
|
||||||
|
<string name="onboarding_home_profile_systems">Sistemas</string>
|
||||||
|
<string name="onboarding_home_profile_features">Recursos</string>
|
||||||
|
<string name="onboarding_home_profile_exterior">Exterior</string>
|
||||||
|
<string name="onboarding_home_profile_interior">Interior</string>
|
||||||
|
<string name="for_you_tab">Para você</string>
|
||||||
|
<string name="browse_tab">Explorar</string>
|
||||||
|
<string name="biometric_lock_title">App bloqueado</string>
|
||||||
|
<string name="biometric_lock_description">Autentique-se para desbloquear o honeyDue</string>
|
||||||
|
<string name="biometric_lock_setting_title">Bloqueio biométrico</string>
|
||||||
|
<string name="biometric_lock_setting_subtitle">Exigir autenticação ao abrir o app</string>
|
||||||
|
<string name="biometric_prompt_title">Desbloquear honeyDue</string>
|
||||||
|
<string name="biometric_prompt_subtitle">Verifique sua identidade para continuar</string>
|
||||||
|
<string name="biometric_unlock_button">Desbloquear com biometria</string>
|
||||||
|
<string name="biometric_auth_failed">Falha na autenticação</string>
|
||||||
|
<string name="biometric_not_available">A autenticação biométrica não está disponível neste dispositivo</string>
|
||||||
|
<string name="notif_channel_task_reminder_name">Lembretes de tarefas</string>
|
||||||
|
<string name="notif_channel_task_reminder_description">Lembretes de tarefas próximas e a vencer</string>
|
||||||
|
<string name="notif_channel_task_overdue_name">Tarefas vencidas</string>
|
||||||
|
<string name="notif_channel_task_overdue_description">Alertas quando uma tarefa passa do prazo</string>
|
||||||
|
<string name="notif_channel_residence_invite_name">Convites para residência</string>
|
||||||
|
<string name="notif_channel_residence_invite_description">Convites para entrar em uma residência compartilhada</string>
|
||||||
|
<string name="notif_channel_subscription_name">Atualizações de assinatura</string>
|
||||||
|
<string name="notif_channel_subscription_description">Atualizações de status e cobrança da assinatura</string>
|
||||||
|
<string name="common_selected">Selecionado</string>
|
||||||
|
<string name="common_open">Abrir</string>
|
||||||
|
<string name="common_skip">Pular</string>
|
||||||
|
<string name="common_or">ou</string>
|
||||||
|
<string name="common_info">Informações</string>
|
||||||
|
<string name="common_verified">Verificado</string>
|
||||||
|
<string name="common_warning">Aviso</string>
|
||||||
|
<string name="common_photo">Foto</string>
|
||||||
|
<string name="common_included">Incluído</string>
|
||||||
|
<string name="common_verifying">Verificando…</string>
|
||||||
|
<string name="time_am">AM</string>
|
||||||
|
<string name="time_pm">PM</string>
|
||||||
|
<string name="time_eve">NOITE</string>
|
||||||
|
<string name="error_network_title">Erro de rede</string>
|
||||||
|
<string name="image_failed_to_load">Falha ao carregar</string>
|
||||||
|
<string name="theme_appearance_title">Aparência</string>
|
||||||
|
<string name="theme_use_system_colors">Usar cores do sistema</string>
|
||||||
|
<string name="theme_material_you_desc">Seguir o Material You do Android 12+ (cores do papel de parede)</string>
|
||||||
|
<string name="paywall_choose_plan_title">Escolha seu plano</string>
|
||||||
|
<string name="paywall_choose_plan_subtitle">Faça upgrade para o Pro e tenha acesso ilimitado</string>
|
||||||
|
<string name="paywall_upgrade_to_pro">Fazer upgrade para o Pro</string>
|
||||||
|
<string name="paywall_col_feature">Recurso</string>
|
||||||
|
<string name="paywall_col_free">Grátis</string>
|
||||||
|
<string name="paywall_col_pro">Pro</string>
|
||||||
|
<string name="paywall_feat_properties">Propriedades</string>
|
||||||
|
<string name="paywall_feat_tasks">Tarefas</string>
|
||||||
|
<string name="paywall_feat_contractors">Prestadores</string>
|
||||||
|
<string name="paywall_feat_documents">Documentos</string>
|
||||||
|
<string name="paywall_val_1_property">1 propriedade</string>
|
||||||
|
<string name="paywall_val_10_tasks">10 tarefas</string>
|
||||||
|
<string name="paywall_val_unlimited">Ilimitado</string>
|
||||||
|
<string name="paywall_val_not_available">Indisponível</string>
|
||||||
|
<string name="reset_check_email_title">Verifique seu e-mail</string>
|
||||||
|
<string name="reset_sent_code_to">Enviamos um código de 6 dígitos para</string>
|
||||||
|
<string name="reset_code_expires">O código expira em 15 minutos</string>
|
||||||
|
<string name="reset_enter_code_hint">Digite o código de 6 dígitos do seu e-mail</string>
|
||||||
|
<string name="reset_code_verified_msg">Código verificado! Agora defina sua nova senha</string>
|
||||||
|
<string name="reset_didnt_receive">Não recebeu o código?</string>
|
||||||
|
<string name="reset_check_spam">Verifique sua pasta de spam se não o encontrar</string>
|
||||||
|
<string name="reset_verify_failed_title">Falha na verificação do código</string>
|
||||||
|
<string name="verify_email_required_msg">A verificação de e-mail é necessária. Verifique sua caixa de entrada para um código de 6 dígitos.</string>
|
||||||
|
<string name="verify_email_invalid_code">Digite um código válido de 6 dígitos</string>
|
||||||
|
<string name="verify_email_didnt_receive">Não recebeu o código? Verifique sua pasta de spam ou entre em contato com o suporte.</string>
|
||||||
|
<string name="verify_email_failed_title">Falha na verificação</string>
|
||||||
|
<string name="reset_pw_success_msg">Sua senha foi redefinida com sucesso</string>
|
||||||
|
<string name="reset_pw_can_login">Agora você pode entrar com sua nova senha</string>
|
||||||
|
<string name="reset_return_to_login">Voltar ao login</string>
|
||||||
|
<string name="reset_set_new_pw_title">Definir nova senha</string>
|
||||||
|
<string name="reset_create_strong_pw">Crie uma senha forte para proteger sua conta</string>
|
||||||
|
<string name="reset_pw_requirements">Requisitos da senha</string>
|
||||||
|
<string name="reset_pw_failed_title">Falha na redefinição da senha</string>
|
||||||
|
<string name="forgot_send_code_hint">Enviaremos um código de verificação de 6 dígitos para este endereço</string>
|
||||||
|
<string name="forgot_check_email_msg">Verifique seu e-mail para um código de verificação de 6 dígitos</string>
|
||||||
|
<string name="forgot_back_to_login">Lembrou sua senha? Voltar ao login</string>
|
||||||
|
<string name="forgot_send_failed_title">Falha ao enviar o código de redefinição</string>
|
||||||
|
<string name="join_property_title">Entrar em propriedade</string>
|
||||||
|
<string name="join_shared_property_header">Entrar em uma propriedade compartilhada</string>
|
||||||
|
<string name="join_enter_code_desc">Digite o código de 6 caracteres fornecido pelo proprietário.</string>
|
||||||
|
<string name="join_share_code_label">Código de compartilhamento</string>
|
||||||
|
<string name="join_share_code_placeholder">ABC123</string>
|
||||||
|
<string name="join_code_helper">Os códigos têm 6 caracteres maiúsculos</string>
|
||||||
|
<string name="join_joining">Entrando…</string>
|
||||||
|
<string name="biometric_enter_pin">Digite o PIN para desbloquear</string>
|
||||||
|
<string name="biometric_pin_label">PIN de 4 dígitos</string>
|
||||||
|
<string name="biometric_incorrect_pin">PIN incorreto</string>
|
||||||
|
<string name="biometric_unlock">Desbloquear</string>
|
||||||
|
<string name="tasks_create_failed_title">Falha ao criar a tarefa</string>
|
||||||
|
<string name="tasks_all_title">Todas as tarefas</string>
|
||||||
|
<string name="tasks_add">Adicionar tarefa</string>
|
||||||
|
<string name="tasks_load_failed_title">Falha ao carregar as tarefas</string>
|
||||||
|
<string name="tasks_all_empty_subtitle">Crie sua primeira tarefa para começar</string>
|
||||||
|
<string name="tasks_add_property_first">Adicione primeiro uma propriedade na aba Residências</string>
|
||||||
|
<string name="residences_upgrade_to_add">Fazer upgrade para adicionar</string>
|
||||||
|
<string name="residences_primary_cd">Residência principal</string>
|
||||||
|
<string name="residence_unit_label">Unidade: %1$s</string>
|
||||||
|
<string name="residence_error_loading_tasks">Erro ao carregar tarefas: %1$s</string>
|
||||||
|
<string name="residence_error_loading_contractors">Erro ao carregar prestadores: %1$s</string>
|
||||||
|
<string name="profile_subscription_cd">Assinatura</string>
|
||||||
|
<string name="onboarding_sub_cancel_note">Cancele quando quiser nas Configurações • Sem compromisso</string>
|
||||||
|
<string name="onboarding_joining_residence">Entrando na residência…</string>
|
||||||
|
<string name="onboarding_sub_trial_legal">Teste gratuito de 7 dias, depois %1$s</string>
|
||||||
|
<string name="common_collapse">Recolher</string>
|
||||||
|
<string name="common_expand">Expandir</string>
|
||||||
|
<string name="common_not_selected">Não selecionado</string>
|
||||||
|
<string name="auth_logging_in">Entrando…</string>
|
||||||
|
<string name="auth_requirement_met">Requisito atendido</string>
|
||||||
|
<string name="auth_requirement_not_met">Requisito não atendido</string>
|
||||||
|
<string name="error_something_wrong">Algo deu errado</string>
|
||||||
|
<string name="paywall_feat_not_included">Não incluído</string>
|
||||||
|
<string name="completions_contractor_prefix">Prestador:</string>
|
||||||
|
<string name="completions_completed_by_prefix">Concluído por:</string>
|
||||||
|
<string name="completions_remove_photo">Remover foto</string>
|
||||||
|
<string name="completions_star_rating_cd">%1$d estrelas</string>
|
||||||
|
<string name="photos_completion_title">Fotos de conclusão</string>
|
||||||
|
<string name="photos_task_completion_cd">Foto de conclusão da tarefa</string>
|
||||||
|
<string name="documents_residence_ref">Residência nº %1$d</string>
|
||||||
|
<string name="documents_task_ref">Tarefa nº %1$d</string>
|
||||||
|
<string name="documents_image_index">Imagem %1$d de %2$d</string>
|
||||||
|
<string name="documents_images_title">Imagens do documento</string>
|
||||||
|
<string name="documents_empty_no_warranties">Nenhuma garantia encontrada</string>
|
||||||
|
<string name="documents_empty_no_documents">Nenhum documento encontrado</string>
|
||||||
|
<string name="documents_days_remaining_count">%1$d dias restantes</string>
|
||||||
|
<string name="contractors_property_ref">Propriedade nº</string>
|
||||||
|
<string name="residence_share_failed">Falha ao compartilhar a residência</string>
|
||||||
|
<string name="theme_default">Padrão</string>
|
||||||
|
<string name="theme_default_desc">Cores vibrantes do sistema iOS</string>
|
||||||
|
<string name="theme_teal">Azul-petróleo</string>
|
||||||
|
<string name="theme_teal_desc">Azul-esverdeado com toques quentes</string>
|
||||||
|
<string name="theme_ocean">Oceano</string>
|
||||||
|
<string name="theme_ocean_desc">Azuis profundos e tons de coral</string>
|
||||||
|
<string name="theme_forest">Floresta</string>
|
||||||
|
<string name="theme_forest_desc">Verdes terrosos e tons dourados</string>
|
||||||
|
<string name="theme_sunset">Pôr do sol</string>
|
||||||
|
<string name="theme_sunset_desc">Laranjas e vermelhos quentes</string>
|
||||||
|
<string name="theme_monochrome">Monocromático</string>
|
||||||
|
<string name="theme_monochrome_desc">Escala de cinza elegante</string>
|
||||||
|
<string name="theme_lavender">Lavanda</string>
|
||||||
|
<string name="theme_lavender_desc">Roxo suave com toques de rosa</string>
|
||||||
|
<string name="theme_crimson">Carmesim</string>
|
||||||
|
<string name="theme_crimson_desc">Vermelho intenso com destaques quentes</string>
|
||||||
|
<string name="theme_midnight">Meia-noite</string>
|
||||||
|
<string name="theme_midnight_desc">Azul-marinho profundo com azul-céu</string>
|
||||||
|
<string name="theme_desert">Deserto</string>
|
||||||
|
<string name="theme_desert_desc">Tons quentes de terracota e areia</string>
|
||||||
|
<string name="theme_mint">Menta</string>
|
||||||
|
<string name="theme_mint_desc">Verde fresco com turquesa</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -472,4 +472,585 @@
|
|||||||
<string name="subscription_features">高级功能</string>
|
<string name="subscription_features">高级功能</string>
|
||||||
<string name="subscription_limit_properties">您已达到当前套餐的房产数量上限</string>
|
<string name="subscription_limit_properties">您已达到当前套餐的房产数量上限</string>
|
||||||
<string name="subscription_limit_tasks">您已达到当前套餐的任务数量上限</string>
|
<string name="subscription_limit_tasks">您已达到当前套餐的任务数量上限</string>
|
||||||
|
<string name="home_profile_heating">供暖</string>
|
||||||
|
<string name="home_profile_cooling">制冷</string>
|
||||||
|
<string name="home_profile_water_heater">热水器</string>
|
||||||
|
<string name="home_profile_pool">泳池</string>
|
||||||
|
<string name="home_profile_sprinkler_system">喷灌系统</string>
|
||||||
|
<string name="home_profile_fireplace">壁炉</string>
|
||||||
|
<string name="home_profile_garage">车库</string>
|
||||||
|
<string name="home_profile_basement">地下室</string>
|
||||||
|
<string name="home_profile_attic">阁楼</string>
|
||||||
|
<string name="home_profile_septic">化粪池</string>
|
||||||
|
<string name="home_profile_roof_type">屋顶类型</string>
|
||||||
|
<string name="home_profile_exterior">外墙</string>
|
||||||
|
<string name="home_profile_flooring">地板</string>
|
||||||
|
<string name="home_profile_landscaping">园艺绿化</string>
|
||||||
|
<string name="onboarding_first_task_selected_count">已选择 %1$d 个任务</string>
|
||||||
|
<string name="onboarding_first_task_add_continue">添加 %1$d 个任务并继续</string>
|
||||||
|
<string name="onboarding_first_task_finding">正在为您的家寻找任务…</string>
|
||||||
|
<string name="onboarding_first_task_no_suggestions">暂无个性化建议 — 浏览完整目录或跳过此步骤。</string>
|
||||||
|
<string name="onboarding_first_task_browse_all">浏览全部</string>
|
||||||
|
<string name="onboarding_first_task_skip">跳过</string>
|
||||||
|
<string name="onboarding_first_task_suggestions_error">无法加载您的建议</string>
|
||||||
|
<string name="onboarding_first_task_connection_error">请检查网络连接后重试。</string>
|
||||||
|
<string name="onboarding_first_task_loading_catalog">正在加载任务目录…</string>
|
||||||
|
<string name="onboarding_first_task_catalog_error">无法加载任务目录</string>
|
||||||
|
<string name="onboarding_first_task_no_templates">当前暂无可用模板。</string>
|
||||||
|
<string name="onboarding_first_task_selected">已选择</string>
|
||||||
|
<string name="onboarding_first_task_offline">离线</string>
|
||||||
|
<string name="onboarding_first_task_skip_for_now">暂时跳过</string>
|
||||||
|
<string name="upgrade_hero_title">升级到 Pro</string>
|
||||||
|
<string name="upgrade_hero_subtitle">释放 honeyDue 的全部潜能</string>
|
||||||
|
<string name="upgrade_choose_plan">选择您的方案</string>
|
||||||
|
<string name="upgrade_plan_yearly">按年</string>
|
||||||
|
<string name="upgrade_plan_monthly">按月</string>
|
||||||
|
<string name="upgrade_plan_save_50">省 50%</string>
|
||||||
|
<string name="upgrade_best_value">最超值</string>
|
||||||
|
<string name="upgrade_whats_included">包含内容</string>
|
||||||
|
<string name="upgrade_feature_unlimited_properties">无限房产</string>
|
||||||
|
<string name="upgrade_feature_unlimited_properties_desc">跟踪所有房屋的维护</string>
|
||||||
|
<string name="upgrade_feature_unlimited_tasks">无限任务</string>
|
||||||
|
<string name="upgrade_feature_unlimited_tasks_desc">再也不会忘记维护任务</string>
|
||||||
|
<string name="upgrade_feature_contractor_management">承包商管理</string>
|
||||||
|
<string name="upgrade_feature_contractor_management_desc">保存并评价您信赖的承包商</string>
|
||||||
|
<string name="upgrade_feature_document_vault">文档保险库</string>
|
||||||
|
<string name="upgrade_feature_document_vault_desc">存储保修单、收据和手册</string>
|
||||||
|
<string name="upgrade_feature_family_sharing">家庭共享</string>
|
||||||
|
<string name="upgrade_feature_family_sharing_desc">邀请家人共同协作</string>
|
||||||
|
<string name="upgrade_feature_smart_reminders">智能提醒</string>
|
||||||
|
<string name="upgrade_feature_smart_reminders_desc">任务到期时收到通知</string>
|
||||||
|
<string name="upgrade_feature_document_warranty_storage">文档与保修单存储</string>
|
||||||
|
<string name="upgrade_subscribe_now">立即订阅</string>
|
||||||
|
<string name="upgrade_restore_purchases">恢复购买</string>
|
||||||
|
<string name="upgrade_terms_text">除非在当前订阅期结束前至少 24 小时取消,否则订阅将自动续订。可在设备设置中管理订阅。</string>
|
||||||
|
<string name="upgrade_terms_of_use">使用条款</string>
|
||||||
|
<string name="upgrade_included">已包含</string>
|
||||||
|
<string name="upgrade_compare_free_vs_pro">对比免费版与 Pro 版</string>
|
||||||
|
<string name="upgrade_maybe_later">以后再说</string>
|
||||||
|
<string name="upgrade_warning">警告</string>
|
||||||
|
<string name="upgrade_subscription_active">订阅已生效</string>
|
||||||
|
<string name="upgrade_subscription_active_message">您现已拥有所有 Pro 功能的完整使用权!</string>
|
||||||
|
<string name="upgrade_feature_required_title">需要升级</string>
|
||||||
|
<string name="upgrade_feature_required_message">此功能需订阅 Pro 才能使用。</string>
|
||||||
|
<string name="upgrade_prompt_default_message">解锁所有功能的无限使用权</string>
|
||||||
|
<string name="upgrade_product_monthly_name">honeyDue Pro 月度</string>
|
||||||
|
<string name="upgrade_product_annual_name">honeyDue Pro 年度</string>
|
||||||
|
<string name="upgrade_billed_monthly">按月计费</string>
|
||||||
|
<string name="upgrade_billed_annually">按年计费</string>
|
||||||
|
<string name="upgrade_save_22">省 22%</string>
|
||||||
|
<string name="tasks_column_done">已完成</string>
|
||||||
|
<string name="tasks_column_archived">已归档</string>
|
||||||
|
<string name="tasks_column_empty">暂无任务</string>
|
||||||
|
<string name="tasks_new_title">新建任务</string>
|
||||||
|
<string name="tasks_title_field_label">标题</string>
|
||||||
|
<string name="tasks_title_placeholder">例如:冲洗热水器</string>
|
||||||
|
<string name="tasks_description_placeholder">可选详情</string>
|
||||||
|
<string name="tasks_due_date_optional_label">截止日期(可选)</string>
|
||||||
|
<string name="tasks_due_date_placeholder_format">yyyy-MM-dd</string>
|
||||||
|
<string name="tasks_due_date_blank_helper">留空表示无截止日期</string>
|
||||||
|
<string name="tasks_estimated_cost_optional_label">预估费用(可选)</string>
|
||||||
|
<string name="suggestions_title">推荐任务</string>
|
||||||
|
<string name="suggestions_skip">跳过</string>
|
||||||
|
<string name="suggestions_accept">接受</string>
|
||||||
|
<string name="suggestions_load_failed">无法加载建议</string>
|
||||||
|
<string name="suggestions_empty_title">暂无建议</string>
|
||||||
|
<string name="suggestions_empty_subtitle">完善您的家庭资料以查看个性化推荐。</string>
|
||||||
|
<string name="completion_history_title">完成历史</string>
|
||||||
|
<string name="completion_history_count_one">%1$d 次完成</string>
|
||||||
|
<string name="completion_history_count_other">%1$d 次完成</string>
|
||||||
|
<string name="completion_history_loading">正在加载完成记录…</string>
|
||||||
|
<string name="completion_history_load_failed">加载完成记录失败</string>
|
||||||
|
<string name="completion_history_empty_title">暂无完成记录</string>
|
||||||
|
<string name="completion_history_empty_message">此任务尚未完成。</string>
|
||||||
|
<string name="completion_history_completed_by">由 %1$s 完成</string>
|
||||||
|
<string name="completion_history_view_photo">查看照片</string>
|
||||||
|
<string name="manage_users_remove_user">移除用户</string>
|
||||||
|
<string name="manage_users_copy_code">复制代码</string>
|
||||||
|
<string name="manage_users_code_copied">代码已复制到剪贴板</string>
|
||||||
|
<string name="manage_users_load_failed">无法加载用户</string>
|
||||||
|
<string name="manage_users_remove_confirm">将 %1$s 从此房产中移除?</string>
|
||||||
|
<string name="properties_shared_users_count">共享用户(%1$d)</string>
|
||||||
|
<string name="properties_no_shared_users">暂无共享用户</string>
|
||||||
|
<string name="properties_shared_users_helper">有权访问此住宅的用户。使用分享按钮邀请他人。</string>
|
||||||
|
<string name="properties_remove_user_confirm">确定要将 %1$s 从此住宅中移除吗?</string>
|
||||||
|
<string name="properties_remove_button">移除</string>
|
||||||
|
<string name="documents_expires_label">到期</string>
|
||||||
|
<string name="auth_password_requirement_length">至少 8 个字符</string>
|
||||||
|
<string name="auth_password_requirement_uppercase">包含一个大写字母</string>
|
||||||
|
<string name="auth_password_requirement_lowercase">包含一个小写字母</string>
|
||||||
|
<string name="auth_password_requirement_digit">包含一个数字</string>
|
||||||
|
<string name="auth_password_requirement_match">密码一致</string>
|
||||||
|
<string name="auth_password_requirements_title">密码要求</string>
|
||||||
|
<string name="auth_password_complexity_error">密码至少需 8 个字符,并包含至少一个大写字母、一个小写字母和一个数字</string>
|
||||||
|
<string name="properties_join_residence_title">加入住所</string>
|
||||||
|
<string name="properties_join_residence_message">您想加入这个共享住所吗?</string>
|
||||||
|
<string name="properties_join_success">已加入住所</string>
|
||||||
|
<string name="properties_join_success_message">您现在可以访问 %1$s。</string>
|
||||||
|
<string name="properties_join_failed">加入失败</string>
|
||||||
|
<string name="properties_joining">正在加入…</string>
|
||||||
|
<string name="properties_shared_by">共享者:%1$s</string>
|
||||||
|
<string name="properties_expires">到期:%1$s</string>
|
||||||
|
<string name="properties_share_upgrade_title">Pro 功能</string>
|
||||||
|
<string name="properties_share_upgrade_message">共享住所是 Pro 功能。升级后可邀请家人共同管理家居维护。</string>
|
||||||
|
<string name="tasks_failed_to_cancel">取消任务失败</string>
|
||||||
|
<string name="tasks_failed_to_restore">恢复任务失败</string>
|
||||||
|
<string name="tasks_failed_to_mark_in_progress">标记任务为进行中失败</string>
|
||||||
|
<string name="tasks_failed_to_archive">归档任务失败</string>
|
||||||
|
<string name="tasks_failed_to_unarchive">取消归档任务失败</string>
|
||||||
|
<string name="tasks_card_in_progress">进行中</string>
|
||||||
|
<string name="tasks_card_actions">操作</string>
|
||||||
|
<string name="tasks_card_mark_in_progress">标记为进行中</string>
|
||||||
|
<string name="tasks_card_complete_task">完成任务</string>
|
||||||
|
<string name="tasks_card_edit_task">编辑任务</string>
|
||||||
|
<string name="tasks_card_cancel_task">取消任务</string>
|
||||||
|
<string name="tasks_card_restore_task">恢复任务</string>
|
||||||
|
<string name="tasks_card_archive_task">归档任务</string>
|
||||||
|
<string name="tasks_card_unarchive_task">取消归档任务</string>
|
||||||
|
<string name="tasks_card_not_available">无</string>
|
||||||
|
<string name="tasks_card_completed_by">完成者:%1$s</string>
|
||||||
|
<string name="tasks_card_cost">费用:$%1$s</string>
|
||||||
|
<string name="tasks_card_view_photos">查看照片(%1$d)</string>
|
||||||
|
<string name="tasks_add_new">添加新任务</string>
|
||||||
|
<string name="tasks_property_required">房产 *</string>
|
||||||
|
<string name="tasks_property_error">房产为必填项</string>
|
||||||
|
<string name="tasks_browse_templates">浏览任务模板</string>
|
||||||
|
<string name="tasks_common_tasks">%1$d 个常见任务</string>
|
||||||
|
<string name="tasks_category_error">类别为必填项</string>
|
||||||
|
<string name="tasks_interval_days">间隔天数</string>
|
||||||
|
<string name="tasks_interval_override">覆盖默认频率间隔</string>
|
||||||
|
<string name="tasks_custom_interval_help">每次重复之间的天数</string>
|
||||||
|
<string name="tasks_due_date_format_error">截止日期为必填项(格式:YYYY-MM-DD)</string>
|
||||||
|
<string name="tasks_due_date_format">格式:YYYY-MM-DD</string>
|
||||||
|
<string name="tasks_create">创建任务</string>
|
||||||
|
<string name="tasks_in_progress_label">进行中</string>
|
||||||
|
<string name="templates_title">任务模板</string>
|
||||||
|
<string name="templates_done">完成</string>
|
||||||
|
<string name="templates_search_placeholder">搜索模板…</string>
|
||||||
|
<string name="templates_clear">清除</string>
|
||||||
|
<string name="templates_result">个结果</string>
|
||||||
|
<string name="templates_results">个结果</string>
|
||||||
|
<string name="templates_no_results_title">未找到模板</string>
|
||||||
|
<string name="templates_no_results_message">请尝试其他搜索词</string>
|
||||||
|
<string name="templates_empty_title">暂无可用模板</string>
|
||||||
|
<string name="templates_empty_message">模板加载后将显示在这里</string>
|
||||||
|
<string name="templates_expand">展开</string>
|
||||||
|
<string name="templates_collapse">收起</string>
|
||||||
|
<string name="templates_add">添加</string>
|
||||||
|
<string name="templates_all_categories">全部</string>
|
||||||
|
<string name="templates_apply">应用</string>
|
||||||
|
<string name="templates_apply_count">应用(%1$d)</string>
|
||||||
|
<string name="templates_selected_count">已选 %1$d 个</string>
|
||||||
|
<string name="templates_retry">重试</string>
|
||||||
|
<string name="templates_load_failed">加载模板失败</string>
|
||||||
|
<string name="templates_create_failed">创建任务失败</string>
|
||||||
|
<string name="completions_complete_task_title">完成任务:%1$s</string>
|
||||||
|
<string name="completions_select_contractor">选择承包商(可选)</string>
|
||||||
|
<string name="completions_choose_contractor_placeholder">选择承包商或留空</string>
|
||||||
|
<string name="completions_expand">展开</string>
|
||||||
|
<string name="completions_none_manual">无(手动输入)</string>
|
||||||
|
<string name="completions_loading_contractors">正在加载承包商…</string>
|
||||||
|
<string name="completions_error_loading_contractors">加载承包商出错</string>
|
||||||
|
<string name="completions_completed_by_name">完成人姓名(可选)</string>
|
||||||
|
<string name="completions_completed_by_placeholder">若不使用上方承包商,请输入姓名</string>
|
||||||
|
<string name="completions_actual_cost_optional">实际费用(可选)</string>
|
||||||
|
<string name="completions_notes_optional">备注(可选)</string>
|
||||||
|
<string name="completions_rating">评分:5 分中的 %1$d 分</string>
|
||||||
|
<string name="completions_add_images">添加图片</string>
|
||||||
|
<string name="completions_take_photo">拍照</string>
|
||||||
|
<string name="completions_choose_from_library">从图库选择</string>
|
||||||
|
<string name="completions_images_selected">已选择 %1$d 张图片</string>
|
||||||
|
<string name="completions_remove_image">移除图片</string>
|
||||||
|
<string name="completions_complete_button">完成</string>
|
||||||
|
<string name="completions_quality_rating">质量评分</string>
|
||||||
|
<string name="completions_photos_count">照片(%1$d/%2$d)</string>
|
||||||
|
<string name="completions_camera">相机</string>
|
||||||
|
<string name="completions_library">图库</string>
|
||||||
|
<string name="completions_add_photos_helper">添加已完成工作的照片(可选)</string>
|
||||||
|
<string name="completions_contractor_helper">将此任务完成记录关联到承包商</string>
|
||||||
|
<string name="completions_details_section">完成详情</string>
|
||||||
|
<string name="completions_optional_info">所有字段均为可选</string>
|
||||||
|
<string name="completions_notes_helper">添加有关已完成工作的备注</string>
|
||||||
|
<string name="completions_notes_placeholder">描述已完成的工作、发现的问题等。</string>
|
||||||
|
<string name="completions_rate_quality">为完成的工作质量评分</string>
|
||||||
|
<string name="completions_enter_manually">在下方手动输入姓名</string>
|
||||||
|
<string name="manage_users_title">管理用户</string>
|
||||||
|
<string name="manage_users_invite_title">邀请他人</string>
|
||||||
|
<string name="manage_users_easy_share">便捷共享</string>
|
||||||
|
<string name="manage_users_send_invite">发送邀请链接</string>
|
||||||
|
<string name="manage_users_easy_share_desc">通过信息、邮件或共享发送 .honeydue 文件。对方只需轻点即可加入。</string>
|
||||||
|
<string name="manage_users_share_code">共享代码</string>
|
||||||
|
<string name="manage_users_no_code">无有效代码</string>
|
||||||
|
<string name="manage_users_generate">生成代码</string>
|
||||||
|
<string name="manage_users_generate_new">生成新代码</string>
|
||||||
|
<string name="manage_users_code_desc">分享这个 6 位字符代码。对方可在应用中输入以加入。</string>
|
||||||
|
<string name="manage_users_users_count">用户(%1$d)</string>
|
||||||
|
<string name="manage_users_owner_badge">所有者</string>
|
||||||
|
<string name="manage_users_remove">移除</string>
|
||||||
|
<string name="manage_users_or">或</string>
|
||||||
|
<string name="contractors_share">共享承包商</string>
|
||||||
|
<string name="contractors_share_upgrade_title">Pro 功能</string>
|
||||||
|
<string name="contractors_share_upgrade_message">共享承包商是 Pro 功能。升级后可将您信赖的承包商分享给亲友。</string>
|
||||||
|
<string name="contractors_import_title">导入承包商</string>
|
||||||
|
<string name="contractors_import_message">您想导入这位承包商吗?</string>
|
||||||
|
<string name="contractors_import_success">承包商已导入</string>
|
||||||
|
<string name="contractors_import_success_message">%1$s 已添加到您的联系人。</string>
|
||||||
|
<string name="contractors_import_failed">导入失败</string>
|
||||||
|
<string name="contractors_shared_by">共享者:%1$s</string>
|
||||||
|
<string name="contractors_form_add_title">添加承包商</string>
|
||||||
|
<string name="contractors_form_edit_title">编辑承包商</string>
|
||||||
|
<string name="contractors_form_basic_info">基本信息</string>
|
||||||
|
<string name="contractors_form_name_required">姓名 *</string>
|
||||||
|
<string name="contractors_form_company">公司</string>
|
||||||
|
<string name="contractors_form_residence_optional">住所(可选)</string>
|
||||||
|
<string name="contractors_form_personal_no_residence">个人(无住所)</string>
|
||||||
|
<string name="contractors_form_personal_visibility">仅您本人可见此承包商</string>
|
||||||
|
<string name="contractors_form_shared_visibility">%1$s 的所有用户都可见此承包商</string>
|
||||||
|
<string name="contractors_form_contact_info">联系信息</string>
|
||||||
|
<string name="contractors_form_phone">电话</string>
|
||||||
|
<string name="contractors_form_email">邮箱</string>
|
||||||
|
<string name="contractors_form_website">网站</string>
|
||||||
|
<string name="contractors_form_specialties">专长</string>
|
||||||
|
<string name="contractors_form_address_section">地址</string>
|
||||||
|
<string name="contractors_form_street_address">街道地址</string>
|
||||||
|
<string name="contractors_form_city">城市</string>
|
||||||
|
<string name="contractors_form_state">州/省</string>
|
||||||
|
<string name="contractors_form_zip_code">邮政编码</string>
|
||||||
|
<string name="contractors_form_notes_section">备注</string>
|
||||||
|
<string name="contractors_form_private_notes">私密备注</string>
|
||||||
|
<string name="contractors_form_mark_favorite">标为收藏</string>
|
||||||
|
<string name="contractors_form_add_button">添加</string>
|
||||||
|
<string name="contractors_form_save_button">保存</string>
|
||||||
|
<string name="documents_form_edit_warranty">编辑保修</string>
|
||||||
|
<string name="documents_form_edit_document">编辑文档</string>
|
||||||
|
<string name="documents_form_add_warranty">添加保修</string>
|
||||||
|
<string name="documents_form_add_document">添加文档</string>
|
||||||
|
<string name="documents_form_select_residence">选择住所</string>
|
||||||
|
<string name="documents_form_residence_required">住所 *</string>
|
||||||
|
<string name="documents_form_document_type_required">文档类型 *</string>
|
||||||
|
<string name="documents_form_title_required">标题 *</string>
|
||||||
|
<string name="documents_form_item_name_required">物品名称 *</string>
|
||||||
|
<string name="documents_form_model_number">型号</string>
|
||||||
|
<string name="documents_form_serial_number">序列号</string>
|
||||||
|
<string name="documents_form_provider_required">提供商/公司 *</string>
|
||||||
|
<string name="documents_form_provider_contact">提供商联系方式</string>
|
||||||
|
<string name="documents_form_claim_phone">理赔电话</string>
|
||||||
|
<string name="documents_form_claim_email">理赔邮箱</string>
|
||||||
|
<string name="documents_form_claim_website">理赔网站</string>
|
||||||
|
<string name="documents_form_purchase_date">购买日期(YYYY-MM-DD)</string>
|
||||||
|
<string name="documents_form_warranty_start">保修开始日期(YYYY-MM-DD)</string>
|
||||||
|
<string name="documents_form_warranty_end_required">保修结束日期(YYYY-MM-DD) *</string>
|
||||||
|
<string name="documents_form_description">描述</string>
|
||||||
|
<string name="documents_form_category">类别</string>
|
||||||
|
<string name="documents_form_select_category">选择类别</string>
|
||||||
|
<string name="documents_form_category_none">无</string>
|
||||||
|
<string name="documents_form_tags">标签</string>
|
||||||
|
<string name="documents_form_tags_placeholder">标签1, 标签2, 标签3</string>
|
||||||
|
<string name="documents_form_notes">备注</string>
|
||||||
|
<string name="documents_form_active">有效</string>
|
||||||
|
<string name="documents_form_existing_photos">现有照片(%1$d)</string>
|
||||||
|
<string name="documents_form_new_photos">新照片(%1$d/%2$d)</string>
|
||||||
|
<string name="documents_form_photos">照片(%1$d/%2$d)</string>
|
||||||
|
<string name="documents_form_camera">相机</string>
|
||||||
|
<string name="documents_form_gallery">图库</string>
|
||||||
|
<string name="documents_form_image_number">图片 %1$d</string>
|
||||||
|
<string name="documents_form_remove_image">移除图片</string>
|
||||||
|
<string name="documents_form_update_warranty">更新保修</string>
|
||||||
|
<string name="documents_form_update_document">更新文档</string>
|
||||||
|
<string name="documents_form_select_residence_error">请选择一个住所</string>
|
||||||
|
<string name="documents_form_title_error">标题为必填项</string>
|
||||||
|
<string name="documents_form_item_name_error">保修需填写物品名称</string>
|
||||||
|
<string name="documents_form_provider_error">保修需填写提供商</string>
|
||||||
|
<string name="documents_form_date_placeholder">2024-01-15</string>
|
||||||
|
<string name="documents_form_date_placeholder_end">2025-01-15</string>
|
||||||
|
<string name="documents_form_failed_to_load_residences">加载住所失败:%1$s</string>
|
||||||
|
<string name="profile_support">支持</string>
|
||||||
|
<string name="profile_contact_support">联系支持</string>
|
||||||
|
<string name="profile_contact_support_subtitle">获取账户帮助</string>
|
||||||
|
<string name="profile_upgrade_benefits_title">解锁高级功能</string>
|
||||||
|
<string name="profile_upgrade_benefits_subtitle">升级到 Pro 享受完整体验</string>
|
||||||
|
<string name="profile_benefit_unlimited_properties">无限房产</string>
|
||||||
|
<string name="profile_benefit_document_vault">文档与保修存储</string>
|
||||||
|
<string name="profile_benefit_residence_sharing">住所共享</string>
|
||||||
|
<string name="profile_benefit_contractor_sharing">承包商共享</string>
|
||||||
|
<string name="profile_benefit_actionable_notifications">可操作通知</string>
|
||||||
|
<string name="profile_benefit_widgets">主屏幕小组件</string>
|
||||||
|
<string name="profile_privacy">隐私政策</string>
|
||||||
|
<string name="profile_privacy_subtitle">查看我们的隐私政策</string>
|
||||||
|
<string name="profile_app_version">版本 %1$s</string>
|
||||||
|
<string name="profile_app_name">honeyDue</string>
|
||||||
|
<string name="profile_edit_profile">编辑个人资料</string>
|
||||||
|
<string name="delete_account_title">删除账户</string>
|
||||||
|
<string name="delete_account_subtitle">永久删除您的账户</string>
|
||||||
|
<string name="delete_account_warning">此操作不可逆转。您的所有数据都将被删除。</string>
|
||||||
|
<string name="delete_account_shared_warning">您拥有的、与其他用户共享的所有住所也将被删除。</string>
|
||||||
|
<string name="delete_account_confirm_password">输入您的密码以确认</string>
|
||||||
|
<string name="delete_account_confirm_type">输入 DELETE 以确认</string>
|
||||||
|
<string name="delete_account_button">删除我的账户</string>
|
||||||
|
<string name="delete_account_cancel">取消</string>
|
||||||
|
<string name="delete_account_success">账户已成功删除</string>
|
||||||
|
<string name="delete_account_failed">删除账户失败</string>
|
||||||
|
<string name="notifications_daily_digest">每日摘要</string>
|
||||||
|
<string name="notifications_daily_digest_desc">每日到期与逾期任务概览</string>
|
||||||
|
<string name="notifications_email_section">邮件通知</string>
|
||||||
|
<string name="notifications_email_task_completed">任务完成邮件</string>
|
||||||
|
<string name="notifications_email_task_completed_desc">任务完成时接收邮件</string>
|
||||||
|
<string name="notifications_set_custom_time">设置自定义时间</string>
|
||||||
|
<string name="notifications_change_time">更改</string>
|
||||||
|
<string name="notifications_select_time">选择通知时间</string>
|
||||||
|
<string name="notifications_master_title">所有通知</string>
|
||||||
|
<string name="notifications_master_desc">一键开启或关闭所有类别</string>
|
||||||
|
<string name="notifications_categories_section">类别</string>
|
||||||
|
<string name="notifications_category_task_reminder">任务提醒</string>
|
||||||
|
<string name="notifications_category_task_reminder_desc">即将到期与快到期的提醒</string>
|
||||||
|
<string name="notifications_category_task_overdue">逾期任务</string>
|
||||||
|
<string name="notifications_category_task_overdue_desc">任务超过截止日期时提醒</string>
|
||||||
|
<string name="notifications_category_residence_invite">住所邀请</string>
|
||||||
|
<string name="notifications_category_residence_invite_desc">加入共享住所的邀请</string>
|
||||||
|
<string name="notifications_category_subscription">订阅更新</string>
|
||||||
|
<string name="notifications_category_subscription_desc">账单和套餐状态变更</string>
|
||||||
|
<string name="notifications_open_system_settings">打开系统设置</string>
|
||||||
|
<string name="notifications_system_settings_desc">在 Android 设置中精细调整声音、角标和勿扰行为</string>
|
||||||
|
<string name="common_share">共享</string>
|
||||||
|
<string name="common_import">导入</string>
|
||||||
|
<string name="common_importing">正在导入…</string>
|
||||||
|
<string name="common_try_again">重试</string>
|
||||||
|
<string name="home_overdue">逾期</string>
|
||||||
|
<string name="home_due_this_week">本周到期</string>
|
||||||
|
<string name="home_next_30_days">未来 30 天</string>
|
||||||
|
<string name="home_your_properties">您的房产</string>
|
||||||
|
<string name="onboarding_welcome_title">欢迎使用 honeyDue</string>
|
||||||
|
<string name="onboarding_welcome_subtitle">您的家居维护好帮手</string>
|
||||||
|
<string name="onboarding_start_fresh">全新开始</string>
|
||||||
|
<string name="onboarding_join_existing">加入现有住所</string>
|
||||||
|
<string name="onboarding_already_have_account">已有账户?登录</string>
|
||||||
|
<string name="onboarding_skip">跳过</string>
|
||||||
|
<string name="onboarding_continue">继续</string>
|
||||||
|
<string name="onboarding_get_started">开始使用</string>
|
||||||
|
<string name="onboarding_feature_tasks_title">再不会忘记任务</string>
|
||||||
|
<string name="onboarding_feature_tasks_desc">通过智能提醒,在一处追踪所有家居维护任务</string>
|
||||||
|
<string name="onboarding_feature_docs_title">文档触手可及</string>
|
||||||
|
<string name="onboarding_feature_docs_desc">安全存储保修单、说明书和收据,随时查阅</string>
|
||||||
|
<string name="onboarding_feature_contractors_title">您信赖的承包商</string>
|
||||||
|
<string name="onboarding_feature_contractors_desc">整理所有承包商联系方式,轻松查找</string>
|
||||||
|
<string name="onboarding_feature_family_title">与家人共享</string>
|
||||||
|
<string name="onboarding_feature_family_desc">邀请家人共同协作管理家居维护</string>
|
||||||
|
<string name="onboarding_feature_notifications_title">智能通知</string>
|
||||||
|
<string name="onboarding_feature_notifications_desc">获取可操作的提醒,直接从通知完成任务</string>
|
||||||
|
<string name="onboarding_feature_widgets_title">主屏幕小组件</string>
|
||||||
|
<string name="onboarding_feature_widgets_desc">直接从主屏幕快速访问任务和提醒</string>
|
||||||
|
<string name="onboarding_location_title">您的住所在哪里?</string>
|
||||||
|
<string name="onboarding_location_subtitle">我们将根据您所在地区的气候推荐相应的维护任务</string>
|
||||||
|
<string name="onboarding_location_use_my_location">使用我的位置</string>
|
||||||
|
<string name="onboarding_location_detecting">正在检测…</string>
|
||||||
|
<string name="onboarding_location_enter_zip">改为输入邮政编码</string>
|
||||||
|
<string name="onboarding_location_enter_zip_prompt">输入您的邮政编码</string>
|
||||||
|
<string name="onboarding_location_zip_placeholder">邮政编码</string>
|
||||||
|
<string name="onboarding_name_residence_title">为您的住所命名</string>
|
||||||
|
<string name="onboarding_name_residence_subtitle">为您的房产取个名字以便识别</string>
|
||||||
|
<string name="onboarding_name_residence_placeholder">例如:我的家、海边别墅、公寓</string>
|
||||||
|
<string name="onboarding_name_residence_hint">稍后可添加更多详情</string>
|
||||||
|
<string name="onboarding_create_account_title">保存您的住所</string>
|
||||||
|
<string name="onboarding_create_account_subtitle">创建账户以跨设备同步</string>
|
||||||
|
<string name="onboarding_create_with_email">用邮箱创建账户</string>
|
||||||
|
<string name="onboarding_verify_email_title">验证您的邮箱</string>
|
||||||
|
<string name="onboarding_verify_email_subtitle">我们已向您的邮箱发送 6 位验证码。请在下方输入以验证账户。</string>
|
||||||
|
<string name="onboarding_verify_email_hint">未收到验证码?请检查垃圾邮件文件夹</string>
|
||||||
|
<string name="onboarding_join_title">加入住所</string>
|
||||||
|
<string name="onboarding_join_subtitle">输入分享给您的 6 位字符代码以加入现有住所</string>
|
||||||
|
<string name="onboarding_join_placeholder">输入共享代码</string>
|
||||||
|
<string name="onboarding_join_button">加入住所</string>
|
||||||
|
<string name="onboarding_tasks_title">一切就绪!</string>
|
||||||
|
<string name="onboarding_tasks_subtitle">让我们从一些任务开始。选得越多,我们越能帮您记牢!</string>
|
||||||
|
<string name="onboarding_tasks_selected">已选 %1$d/%2$d 个任务</string>
|
||||||
|
<string name="onboarding_tasks_add_popular">添加最热门</string>
|
||||||
|
<string name="onboarding_tasks_continue">添加 %1$d 个任务并继续</string>
|
||||||
|
<string name="onboarding_tasks_skip">暂时跳过</string>
|
||||||
|
<string name="onboarding_category_hvac">暖通空调与气候</string>
|
||||||
|
<string name="onboarding_category_safety">安全与防护</string>
|
||||||
|
<string name="onboarding_category_plumbing">管道</string>
|
||||||
|
<string name="onboarding_category_outdoor">户外与草坪</string>
|
||||||
|
<string name="onboarding_category_appliances">家用电器</string>
|
||||||
|
<string name="onboarding_category_general">家居通用</string>
|
||||||
|
<string name="onboarding_subscription_title">升级 Pro</string>
|
||||||
|
<string name="onboarding_subscription_subtitle">让您的家居管理更上一层楼</string>
|
||||||
|
<string name="onboarding_subscription_pro">HONEYDUE PRO</string>
|
||||||
|
<string name="onboarding_subscription_social_proof">4.9 • 1 万多名房主</string>
|
||||||
|
<string name="onboarding_subscription_benefit_properties">无限房产</string>
|
||||||
|
<string name="onboarding_subscription_benefit_properties_desc">追踪您拥有的每一处住所</string>
|
||||||
|
<string name="onboarding_subscription_benefit_reminders">智能提醒</string>
|
||||||
|
<string name="onboarding_subscription_benefit_reminders_desc">绝不错过任何维护期限</string>
|
||||||
|
<string name="onboarding_subscription_benefit_documents">文档保险库</string>
|
||||||
|
<string name="onboarding_subscription_benefit_documents_desc">所有文档集中存放</string>
|
||||||
|
<string name="onboarding_subscription_benefit_family">家庭共享</string>
|
||||||
|
<string name="onboarding_subscription_benefit_family_desc">让全家人保持同步</string>
|
||||||
|
<string name="onboarding_subscription_benefit_insights">支出洞察</string>
|
||||||
|
<string name="onboarding_subscription_benefit_insights_desc">了解您的钱花在哪里</string>
|
||||||
|
<string name="onboarding_subscription_benefit_contractor_sharing">承包商共享</string>
|
||||||
|
<string name="onboarding_subscription_benefit_contractor_sharing_desc">将您信赖的承包商分享给亲友</string>
|
||||||
|
<string name="onboarding_subscription_benefit_widgets">主屏幕小组件</string>
|
||||||
|
<string name="onboarding_subscription_benefit_widgets_desc">直接从主屏幕快速操作</string>
|
||||||
|
<string name="onboarding_subscription_benefit_notifications">可操作通知</string>
|
||||||
|
<string name="onboarding_subscription_benefit_notifications_desc">直接从通知完成任务</string>
|
||||||
|
<string name="onboarding_subscription_choose_plan">选择您的套餐</string>
|
||||||
|
<string name="onboarding_subscription_monthly">按月</string>
|
||||||
|
<string name="onboarding_subscription_yearly">按年</string>
|
||||||
|
<string name="onboarding_subscription_save">省 30%</string>
|
||||||
|
<string name="onboarding_subscription_monthly_price">$2.99/月</string>
|
||||||
|
<string name="onboarding_subscription_yearly_price">$23.99/年</string>
|
||||||
|
<string name="onboarding_subscription_yearly_monthly">仅 $1.99/月</string>
|
||||||
|
<string name="onboarding_subscription_start_trial">开始 7 天免费试用</string>
|
||||||
|
<string name="onboarding_subscription_continue_free">继续使用免费版</string>
|
||||||
|
<string name="onboarding_subscription_trial_terms">7 天免费试用,之后 %1$s。可随时取消。</string>
|
||||||
|
<string name="onboarding_home_profile_title">介绍一下您的住所</string>
|
||||||
|
<string name="onboarding_home_profile_subtitle">全部可选——有助于我们为您定制维护计划</string>
|
||||||
|
<string name="onboarding_home_profile_systems">系统</string>
|
||||||
|
<string name="onboarding_home_profile_features">功能配置</string>
|
||||||
|
<string name="onboarding_home_profile_exterior">外部</string>
|
||||||
|
<string name="onboarding_home_profile_interior">内部</string>
|
||||||
|
<string name="for_you_tab">为您推荐</string>
|
||||||
|
<string name="browse_tab">浏览</string>
|
||||||
|
<string name="biometric_lock_title">应用已锁定</string>
|
||||||
|
<string name="biometric_lock_description">验证身份以解锁 honeyDue</string>
|
||||||
|
<string name="biometric_lock_setting_title">生物识别锁定</string>
|
||||||
|
<string name="biometric_lock_setting_subtitle">打开应用时需要验证身份</string>
|
||||||
|
<string name="biometric_prompt_title">解锁 honeyDue</string>
|
||||||
|
<string name="biometric_prompt_subtitle">验证您的身份以继续</string>
|
||||||
|
<string name="biometric_unlock_button">用生物识别解锁</string>
|
||||||
|
<string name="biometric_auth_failed">验证失败</string>
|
||||||
|
<string name="biometric_not_available">此设备不支持生物识别验证</string>
|
||||||
|
<string name="notif_channel_task_reminder_name">任务提醒</string>
|
||||||
|
<string name="notif_channel_task_reminder_description">即将到期与快到期的任务提醒</string>
|
||||||
|
<string name="notif_channel_task_overdue_name">逾期任务</string>
|
||||||
|
<string name="notif_channel_task_overdue_description">任务超过截止日期时提醒</string>
|
||||||
|
<string name="notif_channel_residence_invite_name">住所邀请</string>
|
||||||
|
<string name="notif_channel_residence_invite_description">加入共享住所的邀请</string>
|
||||||
|
<string name="notif_channel_subscription_name">订阅更新</string>
|
||||||
|
<string name="notif_channel_subscription_description">订阅状态和账单更新</string>
|
||||||
|
<string name="common_selected">已选择</string>
|
||||||
|
<string name="common_open">打开</string>
|
||||||
|
<string name="common_skip">跳过</string>
|
||||||
|
<string name="common_or">或</string>
|
||||||
|
<string name="common_info">信息</string>
|
||||||
|
<string name="common_verified">已验证</string>
|
||||||
|
<string name="common_warning">警告</string>
|
||||||
|
<string name="common_photo">照片</string>
|
||||||
|
<string name="common_included">已包含</string>
|
||||||
|
<string name="common_verifying">正在验证…</string>
|
||||||
|
<string name="time_am">上午</string>
|
||||||
|
<string name="time_pm">下午</string>
|
||||||
|
<string name="time_eve">晚上</string>
|
||||||
|
<string name="error_network_title">网络错误</string>
|
||||||
|
<string name="image_failed_to_load">加载失败</string>
|
||||||
|
<string name="theme_appearance_title">外观</string>
|
||||||
|
<string name="theme_use_system_colors">使用系统颜色</string>
|
||||||
|
<string name="theme_material_you_desc">跟随 Android 12+ Material You(壁纸颜色)</string>
|
||||||
|
<string name="paywall_choose_plan_title">选择您的方案</string>
|
||||||
|
<string name="paywall_choose_plan_subtitle">升级到 Pro 以获得无限访问权限</string>
|
||||||
|
<string name="paywall_upgrade_to_pro">升级到 Pro</string>
|
||||||
|
<string name="paywall_col_feature">功能</string>
|
||||||
|
<string name="paywall_col_free">免费</string>
|
||||||
|
<string name="paywall_col_pro">Pro</string>
|
||||||
|
<string name="paywall_feat_properties">房产</string>
|
||||||
|
<string name="paywall_feat_tasks">任务</string>
|
||||||
|
<string name="paywall_feat_contractors">承包商</string>
|
||||||
|
<string name="paywall_feat_documents">文档</string>
|
||||||
|
<string name="paywall_val_1_property">1 处房产</string>
|
||||||
|
<string name="paywall_val_10_tasks">10 个任务</string>
|
||||||
|
<string name="paywall_val_unlimited">无限</string>
|
||||||
|
<string name="paywall_val_not_available">不可用</string>
|
||||||
|
<string name="reset_check_email_title">查看您的邮箱</string>
|
||||||
|
<string name="reset_sent_code_to">我们已将 6 位验证码发送至</string>
|
||||||
|
<string name="reset_code_expires">验证码将在 15 分钟后过期</string>
|
||||||
|
<string name="reset_enter_code_hint">请输入邮件中的 6 位验证码</string>
|
||||||
|
<string name="reset_code_verified_msg">验证码已验证!现在设置您的新密码</string>
|
||||||
|
<string name="reset_didnt_receive">没有收到验证码?</string>
|
||||||
|
<string name="reset_check_spam">如果没看到,请检查您的垃圾邮件文件夹</string>
|
||||||
|
<string name="reset_verify_failed_title">验证码验证失败</string>
|
||||||
|
<string name="verify_email_required_msg">需要进行邮箱验证。请在收件箱中查看 6 位验证码。</string>
|
||||||
|
<string name="verify_email_invalid_code">请输入有效的 6 位验证码</string>
|
||||||
|
<string name="verify_email_didnt_receive">没有收到验证码?请检查您的垃圾邮件文件夹或联系支持人员。</string>
|
||||||
|
<string name="verify_email_failed_title">验证失败</string>
|
||||||
|
<string name="reset_pw_success_msg">您的密码已成功重置</string>
|
||||||
|
<string name="reset_pw_can_login">您现在可以使用新密码登录</string>
|
||||||
|
<string name="reset_return_to_login">返回登录</string>
|
||||||
|
<string name="reset_set_new_pw_title">设置新密码</string>
|
||||||
|
<string name="reset_create_strong_pw">创建一个强密码以保护您的账户</string>
|
||||||
|
<string name="reset_pw_requirements">密码要求</string>
|
||||||
|
<string name="reset_pw_failed_title">密码重置失败</string>
|
||||||
|
<string name="forgot_send_code_hint">我们将向此地址发送 6 位验证码</string>
|
||||||
|
<string name="forgot_check_email_msg">请查看您的邮箱获取 6 位验证码</string>
|
||||||
|
<string name="forgot_back_to_login">想起密码了?返回登录</string>
|
||||||
|
<string name="forgot_send_failed_title">发送重置验证码失败</string>
|
||||||
|
<string name="join_property_title">加入房产</string>
|
||||||
|
<string name="join_shared_property_header">加入共享房产</string>
|
||||||
|
<string name="join_enter_code_desc">请输入业主提供的 6 位字符共享码。</string>
|
||||||
|
<string name="join_share_code_label">共享码</string>
|
||||||
|
<string name="join_share_code_placeholder">ABC123</string>
|
||||||
|
<string name="join_code_helper">共享码为 6 位大写字符</string>
|
||||||
|
<string name="join_joining">正在加入…</string>
|
||||||
|
<string name="biometric_enter_pin">输入 PIN 以解锁</string>
|
||||||
|
<string name="biometric_pin_label">4 位 PIN</string>
|
||||||
|
<string name="biometric_incorrect_pin">PIN 错误</string>
|
||||||
|
<string name="biometric_unlock">解锁</string>
|
||||||
|
<string name="tasks_create_failed_title">创建任务失败</string>
|
||||||
|
<string name="tasks_all_title">所有任务</string>
|
||||||
|
<string name="tasks_add">添加任务</string>
|
||||||
|
<string name="tasks_load_failed_title">加载任务失败</string>
|
||||||
|
<string name="tasks_all_empty_subtitle">创建您的第一个任务以开始使用</string>
|
||||||
|
<string name="tasks_add_property_first">请先在“住所”标签页中添加房产</string>
|
||||||
|
<string name="residences_upgrade_to_add">升级以添加</string>
|
||||||
|
<string name="residences_primary_cd">主要住所</string>
|
||||||
|
<string name="residence_unit_label">单元:%1$s</string>
|
||||||
|
<string name="residence_error_loading_tasks">加载任务出错:%1$s</string>
|
||||||
|
<string name="residence_error_loading_contractors">加载承包商出错:%1$s</string>
|
||||||
|
<string name="profile_subscription_cd">订阅</string>
|
||||||
|
<string name="onboarding_sub_cancel_note">可随时在设置中取消 • 无需承诺</string>
|
||||||
|
<string name="onboarding_joining_residence">正在加入住所…</string>
|
||||||
|
<string name="onboarding_sub_trial_legal">7 天免费试用,之后 %1$s</string>
|
||||||
|
<string name="common_collapse">收起</string>
|
||||||
|
<string name="common_expand">展开</string>
|
||||||
|
<string name="common_not_selected">未选择</string>
|
||||||
|
<string name="auth_logging_in">正在登录…</string>
|
||||||
|
<string name="auth_requirement_met">已满足要求</string>
|
||||||
|
<string name="auth_requirement_not_met">未满足要求</string>
|
||||||
|
<string name="error_something_wrong">出了点问题</string>
|
||||||
|
<string name="paywall_feat_not_included">不包含</string>
|
||||||
|
<string name="completions_contractor_prefix">承包商:</string>
|
||||||
|
<string name="completions_completed_by_prefix">完成人:</string>
|
||||||
|
<string name="completions_remove_photo">移除照片</string>
|
||||||
|
<string name="completions_star_rating_cd">%1$d 星</string>
|
||||||
|
<string name="photos_completion_title">完成照片</string>
|
||||||
|
<string name="photos_task_completion_cd">任务完成照片</string>
|
||||||
|
<string name="documents_residence_ref">住所 #%1$d</string>
|
||||||
|
<string name="documents_task_ref">任务 #%1$d</string>
|
||||||
|
<string name="documents_image_index">第 %1$d 张,共 %2$d 张</string>
|
||||||
|
<string name="documents_images_title">文档图片</string>
|
||||||
|
<string name="documents_empty_no_warranties">未找到保修</string>
|
||||||
|
<string name="documents_empty_no_documents">未找到文档</string>
|
||||||
|
<string name="documents_days_remaining_count">剩余 %1$d 天</string>
|
||||||
|
<string name="contractors_property_ref">房产 #</string>
|
||||||
|
<string name="residence_share_failed">共享住所失败</string>
|
||||||
|
<string name="theme_default">默认</string>
|
||||||
|
<string name="theme_default_desc">鲜艳的 iOS 系统配色</string>
|
||||||
|
<string name="theme_teal">青色</string>
|
||||||
|
<string name="theme_teal_desc">带暖色调的蓝绿色</string>
|
||||||
|
<string name="theme_ocean">海洋</string>
|
||||||
|
<string name="theme_ocean_desc">深蓝与珊瑚色调</string>
|
||||||
|
<string name="theme_forest">森林</string>
|
||||||
|
<string name="theme_forest_desc">大地绿与金色调</string>
|
||||||
|
<string name="theme_sunset">日落</string>
|
||||||
|
<string name="theme_sunset_desc">温暖的橙色与红色</string>
|
||||||
|
<string name="theme_monochrome">单色</string>
|
||||||
|
<string name="theme_monochrome_desc">优雅的灰阶</string>
|
||||||
|
<string name="theme_lavender">薰衣草</string>
|
||||||
|
<string name="theme_lavender_desc">带粉色调的柔和紫色</string>
|
||||||
|
<string name="theme_crimson">绯红</string>
|
||||||
|
<string name="theme_crimson_desc">带暖色高光的亮红色</string>
|
||||||
|
<string name="theme_midnight">午夜</string>
|
||||||
|
<string name="theme_midnight_desc">深藏青配天蓝色</string>
|
||||||
|
<string name="theme_desert">沙漠</string>
|
||||||
|
<string name="theme_desert_desc">温暖的赤陶与沙色调</string>
|
||||||
|
<string name="theme_mint">薄荷</string>
|
||||||
|
<string name="theme_mint_desc">带绿松石色的清新绿色</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -147,6 +147,11 @@
|
|||||||
<string name="properties_archive_task">Archive Task</string>
|
<string name="properties_archive_task">Archive Task</string>
|
||||||
<string name="properties_archive_task_confirm">Are you sure you want to archive \"%1$s\"? You can unarchive it later from archived tasks.</string>
|
<string name="properties_archive_task_confirm">Are you sure you want to archive \"%1$s\"? You can unarchive it later from archived tasks.</string>
|
||||||
<string name="properties_dismiss">Dismiss</string>
|
<string name="properties_dismiss">Dismiss</string>
|
||||||
|
<string name="properties_shared_users_count">Shared Users (%1$d)</string>
|
||||||
|
<string name="properties_no_shared_users">No shared users</string>
|
||||||
|
<string name="properties_shared_users_helper">Users with access to this residence. Use the share button to invite others.</string>
|
||||||
|
<string name="properties_remove_user_confirm">Are you sure you want to remove %1$s from this residence?</string>
|
||||||
|
<string name="properties_remove_button">Remove</string>
|
||||||
|
|
||||||
<!-- Tasks -->
|
<!-- Tasks -->
|
||||||
<string name="tasks_title">Tasks</string>
|
<string name="tasks_title">Tasks</string>
|
||||||
@@ -216,6 +221,27 @@
|
|||||||
<string name="tasks_column_in_progress">In Progress</string>
|
<string name="tasks_column_in_progress">In Progress</string>
|
||||||
<string name="tasks_column_completed">Completed</string>
|
<string name="tasks_column_completed">Completed</string>
|
||||||
<string name="tasks_column_cancelled">Cancelled</string>
|
<string name="tasks_column_cancelled">Cancelled</string>
|
||||||
|
<string name="tasks_column_done">Done</string>
|
||||||
|
<string name="tasks_column_archived">Archived</string>
|
||||||
|
<string name="tasks_column_empty">No tasks</string>
|
||||||
|
|
||||||
|
<!-- Add Task With Residence -->
|
||||||
|
<string name="tasks_new_title">New Task</string>
|
||||||
|
<string name="tasks_title_field_label">Title</string>
|
||||||
|
<string name="tasks_title_placeholder">e.g. Flush water heater</string>
|
||||||
|
<string name="tasks_description_placeholder">Optional details</string>
|
||||||
|
<string name="tasks_due_date_optional_label">Due date (optional)</string>
|
||||||
|
<string name="tasks_due_date_placeholder_format">yyyy-MM-dd</string>
|
||||||
|
<string name="tasks_due_date_blank_helper">Leave blank for no due date</string>
|
||||||
|
<string name="tasks_estimated_cost_optional_label">Estimated cost (optional)</string>
|
||||||
|
|
||||||
|
<!-- Task Suggestions Screen -->
|
||||||
|
<string name="suggestions_title">Suggested Tasks</string>
|
||||||
|
<string name="suggestions_skip">Skip</string>
|
||||||
|
<string name="suggestions_accept">Accept</string>
|
||||||
|
<string name="suggestions_load_failed">Couldn\'t load suggestions</string>
|
||||||
|
<string name="suggestions_empty_title">No suggestions yet</string>
|
||||||
|
<string name="suggestions_empty_subtitle">Complete your home profile to see personalized recommendations.</string>
|
||||||
|
|
||||||
<!-- Task Actions -->
|
<!-- Task Actions -->
|
||||||
<string name="tasks_mark_complete">Complete</string>
|
<string name="tasks_mark_complete">Complete</string>
|
||||||
@@ -291,6 +317,17 @@
|
|||||||
<string name="completions_rate_quality">Rate the quality of work performed</string>
|
<string name="completions_rate_quality">Rate the quality of work performed</string>
|
||||||
<string name="completions_enter_manually">Enter name manually below</string>
|
<string name="completions_enter_manually">Enter name manually below</string>
|
||||||
|
|
||||||
|
<!-- Completion History Sheet -->
|
||||||
|
<string name="completion_history_title">Completion History</string>
|
||||||
|
<string name="completion_history_count_one">%1$d completion</string>
|
||||||
|
<string name="completion_history_count_other">%1$d completions</string>
|
||||||
|
<string name="completion_history_loading">Loading completions...</string>
|
||||||
|
<string name="completion_history_load_failed">Failed to load completions</string>
|
||||||
|
<string name="completion_history_empty_title">No Completions Yet</string>
|
||||||
|
<string name="completion_history_empty_message">This task has not been completed.</string>
|
||||||
|
<string name="completion_history_completed_by">Completed by %1$s</string>
|
||||||
|
<string name="completion_history_view_photo">View Photo</string>
|
||||||
|
|
||||||
<!-- Manage Users -->
|
<!-- Manage Users -->
|
||||||
<string name="manage_users_title">Manage Users</string>
|
<string name="manage_users_title">Manage Users</string>
|
||||||
<string name="manage_users_invite_title">Invite Others</string>
|
<string name="manage_users_invite_title">Invite Others</string>
|
||||||
@@ -306,6 +343,11 @@
|
|||||||
<string name="manage_users_owner_badge">Owner</string>
|
<string name="manage_users_owner_badge">Owner</string>
|
||||||
<string name="manage_users_remove">Remove</string>
|
<string name="manage_users_remove">Remove</string>
|
||||||
<string name="manage_users_or">or</string>
|
<string name="manage_users_or">or</string>
|
||||||
|
<string name="manage_users_remove_user">Remove user</string>
|
||||||
|
<string name="manage_users_copy_code">Copy code</string>
|
||||||
|
<string name="manage_users_code_copied">Code copied to clipboard</string>
|
||||||
|
<string name="manage_users_load_failed">Couldn\'t load users</string>
|
||||||
|
<string name="manage_users_remove_confirm">Remove %1$s from this property?</string>
|
||||||
|
|
||||||
<!-- Contractors -->
|
<!-- Contractors -->
|
||||||
<string name="contractors_title">Contractors</string>
|
<string name="contractors_title">Contractors</string>
|
||||||
@@ -476,6 +518,7 @@
|
|||||||
<string name="documents_failed_to_load_image">Failed to load image</string>
|
<string name="documents_failed_to_load_image">Failed to load image</string>
|
||||||
<string name="documents_previous">Previous</string>
|
<string name="documents_previous">Previous</string>
|
||||||
<string name="documents_next">Next</string>
|
<string name="documents_next">Next</string>
|
||||||
|
<string name="documents_expires_label">Expires</string>
|
||||||
|
|
||||||
<!-- Document Form -->
|
<!-- Document Form -->
|
||||||
<string name="documents_form_edit_warranty">Edit Warranty</string>
|
<string name="documents_form_edit_warranty">Edit Warranty</string>
|
||||||
@@ -732,6 +775,47 @@
|
|||||||
<string name="subscription_limit_properties">You\'ve reached the property limit for your plan</string>
|
<string name="subscription_limit_properties">You\'ve reached the property limit for your plan</string>
|
||||||
<string name="subscription_limit_tasks">You\'ve reached the task limit for your plan</string>
|
<string name="subscription_limit_tasks">You\'ve reached the task limit for your plan</string>
|
||||||
|
|
||||||
|
<!-- Upgrade Screens -->
|
||||||
|
<string name="upgrade_hero_title">Upgrade to Pro</string>
|
||||||
|
<string name="upgrade_hero_subtitle">Unlock the full potential of honeyDue</string>
|
||||||
|
<string name="upgrade_choose_plan">Choose Your Plan</string>
|
||||||
|
<string name="upgrade_plan_yearly">Yearly</string>
|
||||||
|
<string name="upgrade_plan_monthly">Monthly</string>
|
||||||
|
<string name="upgrade_plan_save_50">Save 50%</string>
|
||||||
|
<string name="upgrade_best_value">BEST VALUE</string>
|
||||||
|
<string name="upgrade_whats_included">What\'s Included</string>
|
||||||
|
<string name="upgrade_feature_unlimited_properties">Unlimited Properties</string>
|
||||||
|
<string name="upgrade_feature_unlimited_properties_desc">Track maintenance for all your homes</string>
|
||||||
|
<string name="upgrade_feature_unlimited_tasks">Unlimited Tasks</string>
|
||||||
|
<string name="upgrade_feature_unlimited_tasks_desc">Never forget a maintenance task again</string>
|
||||||
|
<string name="upgrade_feature_contractor_management">Contractor Management</string>
|
||||||
|
<string name="upgrade_feature_contractor_management_desc">Save and rate your trusted contractors</string>
|
||||||
|
<string name="upgrade_feature_document_vault">Document Vault</string>
|
||||||
|
<string name="upgrade_feature_document_vault_desc">Store warranties, receipts, and manuals</string>
|
||||||
|
<string name="upgrade_feature_family_sharing">Family Sharing</string>
|
||||||
|
<string name="upgrade_feature_family_sharing_desc">Invite family members to collaborate</string>
|
||||||
|
<string name="upgrade_feature_smart_reminders">Smart Reminders</string>
|
||||||
|
<string name="upgrade_feature_smart_reminders_desc">Get notified when tasks are due</string>
|
||||||
|
<string name="upgrade_feature_document_warranty_storage">Document & warranty storage</string>
|
||||||
|
<string name="upgrade_subscribe_now">Subscribe Now</string>
|
||||||
|
<string name="upgrade_restore_purchases">Restore Purchases</string>
|
||||||
|
<string name="upgrade_terms_text">Subscription automatically renews unless cancelled at least 24 hours before the end of the current period. Manage subscriptions in your device settings.</string>
|
||||||
|
<string name="upgrade_terms_of_use">Terms of Use</string>
|
||||||
|
<string name="upgrade_included">Included</string>
|
||||||
|
<string name="upgrade_compare_free_vs_pro">Compare Free vs Pro</string>
|
||||||
|
<string name="upgrade_maybe_later">Maybe Later</string>
|
||||||
|
<string name="upgrade_warning">Warning</string>
|
||||||
|
<string name="upgrade_subscription_active">Subscription Active</string>
|
||||||
|
<string name="upgrade_subscription_active_message">You now have full access to all Pro features!</string>
|
||||||
|
<string name="upgrade_feature_required_title">Upgrade Required</string>
|
||||||
|
<string name="upgrade_feature_required_message">This feature is available with a Pro subscription.</string>
|
||||||
|
<string name="upgrade_prompt_default_message">Unlock unlimited access to all features</string>
|
||||||
|
<string name="upgrade_product_monthly_name">honeyDue Pro Monthly</string>
|
||||||
|
<string name="upgrade_product_annual_name">honeyDue Pro Annual</string>
|
||||||
|
<string name="upgrade_billed_monthly">Billed monthly</string>
|
||||||
|
<string name="upgrade_billed_annually">Billed annually</string>
|
||||||
|
<string name="upgrade_save_22">Save 22%</string>
|
||||||
|
|
||||||
<!-- Onboarding -->
|
<!-- Onboarding -->
|
||||||
<string name="onboarding_welcome_title">Welcome to honeyDue</string>
|
<string name="onboarding_welcome_title">Welcome to honeyDue</string>
|
||||||
<string name="onboarding_welcome_subtitle">Your home maintenance companion</string>
|
<string name="onboarding_welcome_subtitle">Your home maintenance companion</string>
|
||||||
@@ -843,6 +927,38 @@
|
|||||||
<string name="onboarding_home_profile_exterior">Exterior</string>
|
<string name="onboarding_home_profile_exterior">Exterior</string>
|
||||||
<string name="onboarding_home_profile_interior">Interior</string>
|
<string name="onboarding_home_profile_interior">Interior</string>
|
||||||
|
|
||||||
|
<!-- Onboarding - Home Profile Feature Labels -->
|
||||||
|
<string name="home_profile_heating">Heating</string>
|
||||||
|
<string name="home_profile_cooling">Cooling</string>
|
||||||
|
<string name="home_profile_water_heater">Water Heater</string>
|
||||||
|
<string name="home_profile_pool">Pool</string>
|
||||||
|
<string name="home_profile_sprinkler_system">Sprinkler System</string>
|
||||||
|
<string name="home_profile_fireplace">Fireplace</string>
|
||||||
|
<string name="home_profile_garage">Garage</string>
|
||||||
|
<string name="home_profile_basement">Basement</string>
|
||||||
|
<string name="home_profile_attic">Attic</string>
|
||||||
|
<string name="home_profile_septic">Septic</string>
|
||||||
|
<string name="home_profile_roof_type">Roof Type</string>
|
||||||
|
<string name="home_profile_exterior">Exterior</string>
|
||||||
|
<string name="home_profile_flooring">Flooring</string>
|
||||||
|
<string name="home_profile_landscaping">Landscaping</string>
|
||||||
|
|
||||||
|
<!-- Onboarding - First Task Screen -->
|
||||||
|
<string name="onboarding_first_task_selected_count">%1$d tasks selected</string>
|
||||||
|
<string name="onboarding_first_task_add_continue">Add %1$d Tasks & Continue</string>
|
||||||
|
<string name="onboarding_first_task_finding">Finding tasks for your home...</string>
|
||||||
|
<string name="onboarding_first_task_no_suggestions">No personalised suggestions yet — browse the full catalog or skip this step.</string>
|
||||||
|
<string name="onboarding_first_task_browse_all">Browse All</string>
|
||||||
|
<string name="onboarding_first_task_skip">Skip</string>
|
||||||
|
<string name="onboarding_first_task_suggestions_error">Couldn\'t load your suggestions</string>
|
||||||
|
<string name="onboarding_first_task_connection_error">Check your connection and try again.</string>
|
||||||
|
<string name="onboarding_first_task_loading_catalog">Loading the task catalog...</string>
|
||||||
|
<string name="onboarding_first_task_catalog_error">Couldn\'t load the task catalog</string>
|
||||||
|
<string name="onboarding_first_task_no_templates">No templates available right now.</string>
|
||||||
|
<string name="onboarding_first_task_selected">Selected</string>
|
||||||
|
<string name="onboarding_first_task_offline">Offline</string>
|
||||||
|
<string name="onboarding_first_task_skip_for_now">Skip for now</string>
|
||||||
|
|
||||||
<!-- Onboarding - Task Selection Tabs -->
|
<!-- Onboarding - Task Selection Tabs -->
|
||||||
<string name="for_you_tab">For You</string>
|
<string name="for_you_tab">For You</string>
|
||||||
<string name="browse_tab">Browse</string>
|
<string name="browse_tab">Browse</string>
|
||||||
@@ -870,4 +986,186 @@
|
|||||||
<string name="notif_channel_residence_invite_description">Invitations to join a shared residence</string>
|
<string name="notif_channel_residence_invite_description">Invitations to join a shared residence</string>
|
||||||
<string name="notif_channel_subscription_name">Subscription Updates</string>
|
<string name="notif_channel_subscription_name">Subscription Updates</string>
|
||||||
<string name="notif_channel_subscription_description">Subscription status and billing updates</string>
|
<string name="notif_channel_subscription_description">Subscription status and billing updates</string>
|
||||||
|
|
||||||
|
<!-- Localization audit P? — newly extracted hardcoded strings -->
|
||||||
|
|
||||||
|
<!-- Common (reused across screens) -->
|
||||||
|
<string name="common_selected">Selected</string>
|
||||||
|
<string name="common_open">Open</string>
|
||||||
|
<string name="common_skip">Skip</string>
|
||||||
|
<string name="common_or">or</string>
|
||||||
|
<string name="common_info">Info</string>
|
||||||
|
<string name="common_verified">Verified</string>
|
||||||
|
<string name="common_warning">Warning</string>
|
||||||
|
<string name="common_photo">Photo</string>
|
||||||
|
<string name="common_included">Included</string>
|
||||||
|
<string name="common_verifying">Verifying…</string>
|
||||||
|
|
||||||
|
<!-- Time of day -->
|
||||||
|
<string name="time_am">AM</string>
|
||||||
|
<string name="time_pm">PM</string>
|
||||||
|
<string name="time_eve">EVE</string>
|
||||||
|
|
||||||
|
<!-- Error -->
|
||||||
|
<string name="error_network_title">Network Error</string>
|
||||||
|
|
||||||
|
<!-- Image -->
|
||||||
|
<string name="image_failed_to_load">Failed to load</string>
|
||||||
|
|
||||||
|
<!-- Theme selection -->
|
||||||
|
<string name="theme_appearance_title">Appearance</string>
|
||||||
|
<string name="theme_use_system_colors">Use system colors</string>
|
||||||
|
<string name="theme_material_you_desc">Follow Android 12+ Material You (wallpaper colors)</string>
|
||||||
|
|
||||||
|
<!-- Paywall / feature comparison -->
|
||||||
|
<string name="paywall_choose_plan_title">Choose Your Plan</string>
|
||||||
|
<string name="paywall_choose_plan_subtitle">Upgrade to Pro for unlimited access</string>
|
||||||
|
<string name="paywall_upgrade_to_pro">Upgrade to Pro</string>
|
||||||
|
<string name="paywall_col_feature">Feature</string>
|
||||||
|
<string name="paywall_col_free">Free</string>
|
||||||
|
<string name="paywall_col_pro">Pro</string>
|
||||||
|
<string name="paywall_feat_properties">Properties</string>
|
||||||
|
<string name="paywall_feat_tasks">Tasks</string>
|
||||||
|
<string name="paywall_feat_contractors">Contractors</string>
|
||||||
|
<string name="paywall_feat_documents">Documents</string>
|
||||||
|
<string name="paywall_val_1_property">1 property</string>
|
||||||
|
<string name="paywall_val_10_tasks">10 tasks</string>
|
||||||
|
<string name="paywall_val_unlimited">Unlimited</string>
|
||||||
|
<string name="paywall_val_not_available">Not available</string>
|
||||||
|
|
||||||
|
<!-- Password reset — verify code -->
|
||||||
|
<string name="reset_check_email_title">Check Your Email</string>
|
||||||
|
<string name="reset_sent_code_to">We sent a 6-digit code to</string>
|
||||||
|
<string name="reset_code_expires">Code expires in 15 minutes</string>
|
||||||
|
<string name="reset_enter_code_hint">Enter the 6-digit code from your email</string>
|
||||||
|
<string name="reset_code_verified_msg">Code verified! Now set your new password</string>
|
||||||
|
<string name="reset_didnt_receive">Didn\'t receive the code?</string>
|
||||||
|
<string name="reset_check_spam">Check your spam folder if you don\'t see it</string>
|
||||||
|
<string name="reset_verify_failed_title">Code Verification Failed</string>
|
||||||
|
|
||||||
|
<!-- Email verification -->
|
||||||
|
<string name="verify_email_required_msg">Email verification is required. Check your inbox for a 6-digit code.</string>
|
||||||
|
<string name="verify_email_invalid_code">Please enter a valid 6-digit code</string>
|
||||||
|
<string name="verify_email_didnt_receive">Didn\'t receive the code? Check your spam folder or contact support.</string>
|
||||||
|
<string name="verify_email_failed_title">Verification Failed</string>
|
||||||
|
|
||||||
|
<!-- Reset password — set new password -->
|
||||||
|
<string name="reset_pw_success_msg">Your password has been reset successfully</string>
|
||||||
|
<string name="reset_pw_can_login">You can now log in with your new password</string>
|
||||||
|
<string name="reset_return_to_login">Return to Login</string>
|
||||||
|
<string name="reset_set_new_pw_title">Set New Password</string>
|
||||||
|
<string name="reset_create_strong_pw">Create a strong password to secure your account</string>
|
||||||
|
<string name="reset_pw_requirements">Password Requirements</string>
|
||||||
|
<string name="reset_pw_failed_title">Password Reset Failed</string>
|
||||||
|
|
||||||
|
<!-- Forgot password -->
|
||||||
|
<string name="forgot_send_code_hint">We\'ll send a 6-digit verification code to this address</string>
|
||||||
|
<string name="forgot_check_email_msg">Check your email for a 6-digit verification code</string>
|
||||||
|
<string name="forgot_back_to_login">Remember your password? Back to Login</string>
|
||||||
|
<string name="forgot_send_failed_title">Failed to Send Reset Code</string>
|
||||||
|
|
||||||
|
<!-- Join residence -->
|
||||||
|
<string name="join_property_title">Join Property</string>
|
||||||
|
<string name="join_shared_property_header">Join a Shared Property</string>
|
||||||
|
<string name="join_enter_code_desc">Enter the 6-character share code provided by the owner.</string>
|
||||||
|
<string name="join_share_code_label">Share Code</string>
|
||||||
|
<string name="join_share_code_placeholder">ABC123</string>
|
||||||
|
<string name="join_code_helper">Codes are 6 uppercase characters</string>
|
||||||
|
<string name="join_joining">Joining…</string>
|
||||||
|
|
||||||
|
<!-- Biometric lock -->
|
||||||
|
<string name="biometric_enter_pin">Enter PIN to unlock</string>
|
||||||
|
<string name="biometric_pin_label">4-digit PIN</string>
|
||||||
|
<string name="biometric_incorrect_pin">Incorrect PIN</string>
|
||||||
|
<string name="biometric_unlock">Unlock</string>
|
||||||
|
|
||||||
|
<!-- All tasks -->
|
||||||
|
<string name="tasks_create_failed_title">Failed to Create Task</string>
|
||||||
|
<string name="tasks_all_title">All Tasks</string>
|
||||||
|
<string name="tasks_add">Add Task</string>
|
||||||
|
<string name="tasks_load_failed_title">Failed to Load Tasks</string>
|
||||||
|
<string name="tasks_all_empty_subtitle">Create your first task to get started</string>
|
||||||
|
<string name="tasks_add_property_first">Add a property first from the Residences tab</string>
|
||||||
|
|
||||||
|
<!-- Residences -->
|
||||||
|
<string name="residences_upgrade_to_add">Upgrade to Add</string>
|
||||||
|
<string name="residences_primary_cd">Primary residence</string>
|
||||||
|
|
||||||
|
<!-- Residence detail -->
|
||||||
|
<string name="residence_unit_label">Unit: %1$s</string>
|
||||||
|
<string name="residence_error_loading_tasks">Error loading tasks: %1$s</string>
|
||||||
|
<string name="residence_error_loading_contractors">Error loading contractors: %1$s</string>
|
||||||
|
|
||||||
|
<!-- Profile -->
|
||||||
|
<string name="profile_subscription_cd">Subscription</string>
|
||||||
|
|
||||||
|
<!-- Onboarding -->
|
||||||
|
<string name="onboarding_sub_cancel_note">Cancel anytime in Settings • No commitment</string>
|
||||||
|
<string name="onboarding_joining_residence">Joining residence…</string>
|
||||||
|
<string name="onboarding_sub_trial_legal">7-day free trial, then %1$s</string>
|
||||||
|
|
||||||
|
<!-- i18n batch: common -->
|
||||||
|
<string name="common_collapse">Collapse</string>
|
||||||
|
<string name="common_expand">Expand</string>
|
||||||
|
<string name="common_not_selected">Not selected</string>
|
||||||
|
|
||||||
|
<!-- i18n batch: auth -->
|
||||||
|
<string name="auth_logging_in">Logging in…</string>
|
||||||
|
<string name="auth_requirement_met">Requirement met</string>
|
||||||
|
<string name="auth_requirement_not_met">Requirement not met</string>
|
||||||
|
|
||||||
|
<!-- i18n batch: errors -->
|
||||||
|
<string name="error_something_wrong">Something went wrong</string>
|
||||||
|
|
||||||
|
<!-- i18n batch: subscription / paywall -->
|
||||||
|
<string name="paywall_feat_not_included">Not included</string>
|
||||||
|
|
||||||
|
<!-- i18n batch: completions -->
|
||||||
|
<string name="completions_contractor_prefix">Contractor:</string>
|
||||||
|
<string name="completions_completed_by_prefix">Completed by:</string>
|
||||||
|
<string name="completions_remove_photo">Remove photo</string>
|
||||||
|
<string name="completions_star_rating_cd">%1$d stars</string>
|
||||||
|
|
||||||
|
<!-- i18n batch: photos -->
|
||||||
|
<string name="photos_completion_title">Completion Photos</string>
|
||||||
|
<string name="photos_task_completion_cd">Task completion photo</string>
|
||||||
|
|
||||||
|
<!-- i18n batch: documents -->
|
||||||
|
<string name="documents_residence_ref">Residence #%1$d</string>
|
||||||
|
<string name="documents_task_ref">Task #%1$d</string>
|
||||||
|
<string name="documents_image_index">Image %1$d of %2$d</string>
|
||||||
|
<string name="documents_images_title">Document Images</string>
|
||||||
|
<string name="documents_empty_no_warranties">No warranties found</string>
|
||||||
|
<string name="documents_empty_no_documents">No documents found</string>
|
||||||
|
<string name="documents_days_remaining_count">%1$d days remaining</string>
|
||||||
|
|
||||||
|
<!-- i18n batch: contractors -->
|
||||||
|
<string name="contractors_property_ref">Property #</string>
|
||||||
|
|
||||||
|
<!-- i18n batch: residence -->
|
||||||
|
<string name="residence_share_failed">Failed to share residence</string>
|
||||||
|
|
||||||
|
<!-- i18n batch: themes -->
|
||||||
|
<string name="theme_default">Default</string>
|
||||||
|
<string name="theme_default_desc">Vibrant iOS system colors</string>
|
||||||
|
<string name="theme_teal">Teal</string>
|
||||||
|
<string name="theme_teal_desc">Blue-green with warm accents</string>
|
||||||
|
<string name="theme_ocean">Ocean</string>
|
||||||
|
<string name="theme_ocean_desc">Deep blues and coral tones</string>
|
||||||
|
<string name="theme_forest">Forest</string>
|
||||||
|
<string name="theme_forest_desc">Earth greens and golden hues</string>
|
||||||
|
<string name="theme_sunset">Sunset</string>
|
||||||
|
<string name="theme_sunset_desc">Warm oranges and reds</string>
|
||||||
|
<string name="theme_monochrome">Monochrome</string>
|
||||||
|
<string name="theme_monochrome_desc">Elegant grayscale</string>
|
||||||
|
<string name="theme_lavender">Lavender</string>
|
||||||
|
<string name="theme_lavender_desc">Soft purple with pink accents</string>
|
||||||
|
<string name="theme_crimson">Crimson</string>
|
||||||
|
<string name="theme_crimson_desc">Bold red with warm highlights</string>
|
||||||
|
<string name="theme_midnight">Midnight</string>
|
||||||
|
<string name="theme_midnight_desc">Deep navy with sky blue</string>
|
||||||
|
<string name="theme_desert">Desert</string>
|
||||||
|
<string name="theme_desert_desc">Warm terracotta and sand tones</string>
|
||||||
|
<string name="theme_mint">Mint</string>
|
||||||
|
<string name="theme_mint_desc">Fresh green with turquoise</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -109,6 +109,24 @@ object DataManager : IDataManager {
|
|||||||
private val _currentUser = MutableStateFlow<User?>(null)
|
private val _currentUser = MutableStateFlow<User?>(null)
|
||||||
override val currentUser: StateFlow<User?> = _currentUser.asStateFlow()
|
override val currentUser: StateFlow<User?> = _currentUser.asStateFlow()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flow ID of a pending Kratos verification flow.
|
||||||
|
*
|
||||||
|
* Set in [AuthApi] right after a registration response carries a
|
||||||
|
* `continue_with` item of action `show_verification_ui`. Cleared on
|
||||||
|
* successful verification. While set, [AuthApi.verifyEmail] posts the
|
||||||
|
* 6-digit code to this exact flow rather than initialising a fresh one
|
||||||
|
* (the auto-emailed code is bound to a specific flow ID — posting it
|
||||||
|
* to a new flow always fails).
|
||||||
|
*
|
||||||
|
* In-memory only. Kratos's default verification flow lifespan is 1h,
|
||||||
|
* which is plenty for the user to receive the email and enter the
|
||||||
|
* code. If they kill the app between sign-up and entering the code
|
||||||
|
* they'll need a fresh code (handled by the resend path).
|
||||||
|
*/
|
||||||
|
private val _pendingVerificationFlowId = MutableStateFlow<String?>(null)
|
||||||
|
val pendingVerificationFlowId: StateFlow<String?> = _pendingVerificationFlowId.asStateFlow()
|
||||||
|
|
||||||
// ==================== APP PREFERENCES ====================
|
// ==================== APP PREFERENCES ====================
|
||||||
|
|
||||||
private val _themeId = MutableStateFlow("default")
|
private val _themeId = MutableStateFlow("default")
|
||||||
@@ -230,6 +248,18 @@ object DataManager : IDataManager {
|
|||||||
private val _contractorSpecialtiesMap = MutableStateFlow<Map<Int, ContractorSpecialty>>(emptyMap())
|
private val _contractorSpecialtiesMap = MutableStateFlow<Map<Int, ContractorSpecialty>>(emptyMap())
|
||||||
val contractorSpecialtiesMap: StateFlow<Map<Int, ContractorSpecialty>> = _contractorSpecialtiesMap.asStateFlow()
|
val contractorSpecialtiesMap: StateFlow<Map<Int, ContractorSpecialty>> = _contractorSpecialtiesMap.asStateFlow()
|
||||||
|
|
||||||
|
// Home-profile field options (heating_type -> [options], etc.), served
|
||||||
|
// localized by the backend. Replaces the previously hardcoded client lists.
|
||||||
|
private val _homeProfileOptions = MutableStateFlow<Map<String, List<HomeProfileOption>>>(emptyMap())
|
||||||
|
val homeProfileOptions: StateFlow<Map<String, List<HomeProfileOption>>> = _homeProfileOptions.asStateFlow()
|
||||||
|
|
||||||
|
// Document type / category options ({value, display_name}), served localized.
|
||||||
|
private val _documentTypes = MutableStateFlow<List<HomeProfileOption>>(emptyList())
|
||||||
|
val documentTypes: StateFlow<List<HomeProfileOption>> = _documentTypes.asStateFlow()
|
||||||
|
|
||||||
|
private val _documentCategories = MutableStateFlow<List<HomeProfileOption>>(emptyList())
|
||||||
|
val documentCategories: StateFlow<List<HomeProfileOption>> = _documentCategories.asStateFlow()
|
||||||
|
|
||||||
// ==================== STATE METADATA ====================
|
// ==================== STATE METADATA ====================
|
||||||
|
|
||||||
private val _isInitialized = MutableStateFlow(false)
|
private val _isInitialized = MutableStateFlow(false)
|
||||||
@@ -302,6 +332,16 @@ object DataManager : IDataManager {
|
|||||||
persistToDisk()
|
persistToDisk()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the in-flight Kratos verification flow ID. AuthApi calls this with
|
||||||
|
* the value pulled out of the registration response's `continue_with`
|
||||||
|
* `show_verification_ui` item. Pass `null` to clear (e.g. on successful
|
||||||
|
* verification or sign-out).
|
||||||
|
*/
|
||||||
|
fun setPendingVerificationFlowId(flowId: String?) {
|
||||||
|
_pendingVerificationFlowId.value = flowId
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== THEME UPDATE METHODS ====================
|
// ==================== THEME UPDATE METHODS ====================
|
||||||
|
|
||||||
fun setThemeId(id: String) {
|
fun setThemeId(id: String) {
|
||||||
@@ -796,6 +836,15 @@ object DataManager : IDataManager {
|
|||||||
setTaskCategories(seededData.taskCategories)
|
setTaskCategories(seededData.taskCategories)
|
||||||
setContractorSpecialties(seededData.contractorSpecialties)
|
setContractorSpecialties(seededData.contractorSpecialties)
|
||||||
setTaskTemplatesGrouped(seededData.taskTemplates)
|
setTaskTemplatesGrouped(seededData.taskTemplates)
|
||||||
|
if (seededData.homeProfileOptions.isNotEmpty()) {
|
||||||
|
_homeProfileOptions.value = seededData.homeProfileOptions
|
||||||
|
}
|
||||||
|
if (seededData.documentTypes.isNotEmpty()) {
|
||||||
|
_documentTypes.value = seededData.documentTypes
|
||||||
|
}
|
||||||
|
if (seededData.documentCategories.isNotEmpty()) {
|
||||||
|
_documentCategories.value = seededData.documentCategories
|
||||||
|
}
|
||||||
setSeededDataETag(etag)
|
setSeededDataETag(etag)
|
||||||
_lookupsInitialized.value = true
|
_lookupsInitialized.value = true
|
||||||
// Persist lookups to disk for faster startup
|
// Persist lookups to disk for faster startup
|
||||||
@@ -862,6 +911,9 @@ object DataManager : IDataManager {
|
|||||||
_taskCategoriesMap.value = emptyMap()
|
_taskCategoriesMap.value = emptyMap()
|
||||||
_contractorSpecialties.value = emptyList()
|
_contractorSpecialties.value = emptyList()
|
||||||
_contractorSpecialtiesMap.value = emptyMap()
|
_contractorSpecialtiesMap.value = emptyMap()
|
||||||
|
_homeProfileOptions.value = emptyMap()
|
||||||
|
_documentTypes.value = emptyList()
|
||||||
|
_documentCategories.value = emptyList()
|
||||||
_taskTemplates.value = emptyList()
|
_taskTemplates.value = emptyList()
|
||||||
_taskTemplatesGrouped.value = null
|
_taskTemplatesGrouped.value = null
|
||||||
_lookupsInitialized.value = false
|
_lookupsInitialized.value = false
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.tt.honeyDue.i18n
|
||||||
|
|
||||||
|
import com.tt.honeyDue.network.getDeviceLanguage
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lightweight, dependency-free client-side localization for the SHARED Kotlin
|
||||||
|
* layer (errors, date words, fallback labels) that the iOS String Catalog and
|
||||||
|
* Android resources can't reach because they originate in commonMain.
|
||||||
|
*
|
||||||
|
* Real API error messages are localized by the backend (Accept-Language); this
|
||||||
|
* covers client-generated / offline / fallback copy only.
|
||||||
|
*
|
||||||
|
* Resolution: the device language (2-letter) from [getDeviceLanguage], falling
|
||||||
|
* back to English. Use [t] for plain lookups and [t] with args for `{0}`-style
|
||||||
|
* placeholder substitution.
|
||||||
|
*/
|
||||||
|
object ClientStrings {
|
||||||
|
private val supported = setOf("en", "es", "fr", "de", "pt", "it", "ja", "ko", "nl", "zh")
|
||||||
|
|
||||||
|
private fun lang(): String {
|
||||||
|
val l = getDeviceLanguage().lowercase().take(2)
|
||||||
|
return if (l in supported) l else "en"
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Localized string for [key]; falls back to English, then to the key itself. */
|
||||||
|
fun t(key: String): String {
|
||||||
|
val byLang = strings[key] ?: return key
|
||||||
|
return byLang[lang()] ?: byLang["en"] ?: key
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Localized string with `{0}`, `{1}`, … placeholder substitution. */
|
||||||
|
fun t(key: String, vararg args: Any?): String {
|
||||||
|
var s = t(key)
|
||||||
|
args.forEachIndexed { i, a -> s = s.replace("{$i}", a.toString()) }
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// key -> (lang -> value). English is authoritative; other languages are
|
||||||
|
// populated by the translation pipeline. GENERATED-BLOCK:strings
|
||||||
|
private val strings: Map<String, Map<String, String>> = STRINGS
|
||||||
|
}
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
package com.tt.honeyDue.i18n
|
||||||
|
|
||||||
|
// GENERATED string table for [ClientStrings]. English authoritative; other
|
||||||
|
// languages machine-translated (pending native QA). Keep keys stable.
|
||||||
|
internal val STRINGS: Map<String, Map<String, String>> = mapOf(
|
||||||
|
"date.today" to mapOf("en" to "Today", "es" to "Hoy", "fr" to "Aujourd'hui", "de" to "Heute", "pt" to "Hoje", "it" to "Oggi", "ja" to "今日", "ko" to "오늘", "nl" to "Vandaag", "zh" to "今天"),
|
||||||
|
"date.tomorrow" to mapOf("en" to "Tomorrow", "es" to "Mañana", "fr" to "Demain", "de" to "Morgen", "pt" to "Amanhã", "it" to "Domani", "ja" to "明日", "ko" to "내일", "nl" to "Morgen", "zh" to "明天"),
|
||||||
|
"date.yesterday" to mapOf("en" to "Yesterday", "es" to "Ayer", "fr" to "Hier", "de" to "Gestern", "pt" to "Ontem", "it" to "Ieri", "ja" to "昨日", "ko" to "어제", "nl" to "Gisteren", "zh" to "昨天"),
|
||||||
|
"date.in_days" to mapOf("en" to "in {0} days", "es" to "en {0} días", "fr" to "dans {0} jours", "de" to "in {0} Tagen", "pt" to "em {0} dias", "it" to "tra {0} giorni", "ja" to "{0}日後", "ko" to "{0}일 후", "nl" to "over {0} dagen", "zh" to "{0}天后"),
|
||||||
|
"date.days_ago" to mapOf("en" to "{0} days ago", "es" to "hace {0} días", "fr" to "il y a {0} jours", "de" to "vor {0} Tagen", "pt" to "há {0} dias", "it" to "{0} giorni fa", "ja" to "{0}日前", "ko" to "{0}일 전", "nl" to "{0} dagen geleden", "zh" to "{0}天前"),
|
||||||
|
"date.at" to mapOf("en" to "at", "es" to "a las", "fr" to "à", "de" to "um", "pt" to "às", "it" to "alle", "ja" to "", "ko" to "", "nl" to "om", "zh" to ""),
|
||||||
|
"time.am" to mapOf("en" to "AM", "es" to "AM", "fr" to "AM", "de" to "AM", "pt" to "AM", "it" to "AM", "ja" to "午前", "ko" to "오전", "nl" to "AM", "zh" to "上午"),
|
||||||
|
"time.pm" to mapOf("en" to "PM", "es" to "PM", "fr" to "PM", "de" to "PM", "pt" to "PM", "it" to "PM", "ja" to "午後", "ko" to "오후", "nl" to "PM", "zh" to "下午"),
|
||||||
|
"month.1" to mapOf("en" to "Jan", "es" to "ene", "fr" to "janv.", "de" to "Jan", "pt" to "jan", "it" to "gen", "ja" to "1月", "ko" to "1월", "nl" to "jan", "zh" to "1月"),
|
||||||
|
"month.2" to mapOf("en" to "Feb", "es" to "feb", "fr" to "févr.", "de" to "Feb", "pt" to "fev", "it" to "feb", "ja" to "2月", "ko" to "2월", "nl" to "feb", "zh" to "2月"),
|
||||||
|
"month.3" to mapOf("en" to "Mar", "es" to "mar", "fr" to "mars", "de" to "Mär", "pt" to "mar", "it" to "mar", "ja" to "3月", "ko" to "3월", "nl" to "mrt", "zh" to "3月"),
|
||||||
|
"month.4" to mapOf("en" to "Apr", "es" to "abr", "fr" to "avr.", "de" to "Apr", "pt" to "abr", "it" to "apr", "ja" to "4月", "ko" to "4월", "nl" to "apr", "zh" to "4月"),
|
||||||
|
"month.5" to mapOf("en" to "May", "es" to "may", "fr" to "mai", "de" to "Mai", "pt" to "mai", "it" to "mag", "ja" to "5月", "ko" to "5월", "nl" to "mei", "zh" to "5月"),
|
||||||
|
"month.6" to mapOf("en" to "Jun", "es" to "jun", "fr" to "juin", "de" to "Jun", "pt" to "jun", "it" to "giu", "ja" to "6月", "ko" to "6월", "nl" to "jun", "zh" to "6月"),
|
||||||
|
"month.7" to mapOf("en" to "Jul", "es" to "jul", "fr" to "juil.", "de" to "Jul", "pt" to "jul", "it" to "lug", "ja" to "7月", "ko" to "7월", "nl" to "jul", "zh" to "7月"),
|
||||||
|
"month.8" to mapOf("en" to "Aug", "es" to "ago", "fr" to "août", "de" to "Aug", "pt" to "ago", "it" to "ago", "ja" to "8月", "ko" to "8월", "nl" to "aug", "zh" to "8月"),
|
||||||
|
"month.9" to mapOf("en" to "Sep", "es" to "sep", "fr" to "sept.", "de" to "Sep", "pt" to "set", "it" to "set", "ja" to "9月", "ko" to "9월", "nl" to "sep", "zh" to "9月"),
|
||||||
|
"month.10" to mapOf("en" to "Oct", "es" to "oct", "fr" to "oct.", "de" to "Okt", "pt" to "out", "it" to "ott", "ja" to "10月", "ko" to "10월", "nl" to "okt", "zh" to "10月"),
|
||||||
|
"month.11" to mapOf("en" to "Nov", "es" to "nov", "fr" to "nov.", "de" to "Nov", "pt" to "nov", "it" to "nov", "ja" to "11月", "ko" to "11월", "nl" to "nov", "zh" to "11月"),
|
||||||
|
"month.12" to mapOf("en" to "Dec", "es" to "dic", "fr" to "déc.", "de" to "Dez", "pt" to "dez", "it" to "dic", "ja" to "12月", "ko" to "12월", "nl" to "dec", "zh" to "12月"),
|
||||||
|
"err.unknown" to mapOf("en" to "Unknown error", "es" to "Error desconocido", "fr" to "Erreur inconnue", "de" to "Unbekannter Fehler", "pt" to "Erro desconhecido", "it" to "Errore sconosciuto", "ja" to "不明なエラー", "ko" to "알 수 없는 오류", "nl" to "Onbekende fout", "zh" to "未知错误"),
|
||||||
|
"err.unknown_occurred" to mapOf("en" to "Unknown error occurred", "es" to "Ocurrió un error desconocido", "fr" to "Une erreur inconnue est survenue", "de" to "Ein unbekannter Fehler ist aufgetreten", "pt" to "Ocorreu um erro desconhecido", "it" to "Si è verificato un errore sconosciuto", "ja" to "不明なエラーが発生しました", "ko" to "알 수 없는 오류가 발생했습니다", "nl" to "Er is een onbekende fout opgetreden", "zh" to "发生未知错误"),
|
||||||
|
"err.not_authenticated" to mapOf("en" to "Not authenticated", "es" to "No autenticado", "fr" to "Non authentifié", "de" to "Nicht angemeldet", "pt" to "Não autenticado", "it" to "Non autenticato", "ja" to "認証されていません", "ko" to "인증되지 않았습니다", "nl" to "Niet ingelogd", "zh" to "未通过身份验证"),
|
||||||
|
"err.generic" to mapOf("en" to "Something went wrong. Please try again.", "es" to "Algo salió mal. Inténtalo de nuevo.", "fr" to "Une erreur s'est produite. Veuillez réessayer.", "de" to "Etwas ist schiefgelaufen. Bitte versuche es erneut.", "pt" to "Algo deu errado. Tente novamente.", "it" to "Qualcosa è andato storto. Riprova.", "ja" to "問題が発生しました。もう一度お試しください。", "ko" to "문제가 발생했습니다. 다시 시도해 주세요.", "nl" to "Er is iets misgegaan. Probeer het opnieuw.", "zh" to "出错了,请重试。"),
|
||||||
|
"err.generic_retry" to mapOf("en" to "An error occurred. Please try again.", "es" to "Ocurrió un error. Inténtalo de nuevo.", "fr" to "Une erreur est survenue. Veuillez réessayer.", "de" to "Ein Fehler ist aufgetreten. Bitte versuche es erneut.", "pt" to "Ocorreu um erro. Tente novamente.", "it" to "Si è verificato un errore. Riprova.", "ja" to "エラーが発生しました。もう一度お試しください。", "ko" to "오류가 발생했습니다. 다시 시도해 주세요.", "nl" to "Er is een fout opgetreden. Probeer het opnieuw.", "zh" to "发生错误,请重试。"),
|
||||||
|
"err.request_failed" to mapOf("en" to "Request failed. Please check your input and try again.", "es" to "La solicitud falló. Revisa los datos e inténtalo de nuevo.", "fr" to "Échec de la requête. Vérifiez votre saisie et réessayez.", "de" to "Anfrage fehlgeschlagen. Bitte überprüfe deine Eingabe und versuche es erneut.", "pt" to "Falha na solicitação. Verifique os dados e tente novamente.", "it" to "Richiesta non riuscita. Controlla i dati inseriti e riprova.", "ja" to "リクエストに失敗しました。入力内容を確認してもう一度お試しください。", "ko" to "요청에 실패했습니다. 입력 내용을 확인하고 다시 시도해 주세요.", "nl" to "Verzoek mislukt. Controleer je invoer en probeer het opnieuw.", "zh" to "请求失败,请检查输入后重试。"),
|
||||||
|
"err.net.no_connection" to mapOf("en" to "Unable to connect to the server. Please check your internet connection.", "es" to "No se pudo conectar con el servidor. Revisa tu conexión a internet.", "fr" to "Impossible de se connecter au serveur. Vérifiez votre connexion Internet.", "de" to "Verbindung zum Server nicht möglich. Bitte überprüfe deine Internetverbindung.", "pt" to "Não foi possível conectar ao servidor. Verifique sua conexão com a internet.", "it" to "Impossibile connettersi al server. Controlla la connessione a Internet.", "ja" to "サーバーに接続できません。インターネット接続を確認してください。", "ko" to "서버에 연결할 수 없습니다. 인터넷 연결을 확인해 주세요.", "nl" to "Kan geen verbinding maken met de server. Controleer je internetverbinding.", "zh" to "无法连接到服务器,请检查网络连接。"),
|
||||||
|
"err.net.timeout" to mapOf("en" to "Request timed out. Please try again.", "es" to "Se agotó el tiempo de espera. Inténtalo de nuevo.", "fr" to "Délai d'attente dépassé. Veuillez réessayer.", "de" to "Zeitüberschreitung der Anfrage. Bitte versuche es erneut.", "pt" to "A solicitação expirou. Tente novamente.", "it" to "Richiesta scaduta. Riprova.", "ja" to "リクエストがタイムアウトしました。もう一度お試しください。", "ko" to "요청 시간이 초과되었습니다. 다시 시도해 주세요.", "nl" to "Verzoek verlopen. Probeer het opnieuw.", "zh" to "请求超时,请重试。"),
|
||||||
|
"err.net.no_internet" to mapOf("en" to "No internet connection. Please check your network settings.", "es" to "Sin conexión a internet. Revisa la configuración de tu red.", "fr" to "Pas de connexion Internet. Vérifiez vos paramètres réseau.", "de" to "Keine Internetverbindung. Bitte überprüfe deine Netzwerkeinstellungen.", "pt" to "Sem conexão com a internet. Verifique as configurações de rede.", "it" to "Nessuna connessione a Internet. Controlla le impostazioni di rete.", "ja" to "インターネットに接続されていません。ネットワーク設定を確認してください。", "ko" to "인터넷 연결이 없습니다. 네트워크 설정을 확인해 주세요.", "nl" to "Geen internetverbinding. Controleer je netwerkinstellingen.", "zh" to "无网络连接,请检查网络设置。"),
|
||||||
|
"err.net.server_down" to mapOf("en" to "Unable to connect to the server. The server may be down.", "es" to "No se pudo conectar con el servidor. Es posible que esté caído.", "fr" to "Impossible de se connecter au serveur. Le serveur est peut-être indisponible.", "de" to "Verbindung zum Server nicht möglich. Der Server ist möglicherweise nicht erreichbar.", "pt" to "Não foi possível conectar ao servidor. O servidor pode estar fora do ar.", "it" to "Impossibile connettersi al server. Il server potrebbe non essere disponibile.", "ja" to "サーバーに接続できません。サーバーがダウンしている可能性があります。", "ko" to "서버에 연결할 수 없습니다. 서버가 다운되었을 수 있습니다.", "nl" to "Kan geen verbinding maken met de server. De server is mogelijk offline.", "zh" to "无法连接到服务器,服务器可能已停止运行。"),
|
||||||
|
"err.net.interrupted" to mapOf("en" to "Connection was interrupted. Please try again.", "es" to "Se interrumpió la conexión. Inténtalo de nuevo.", "fr" to "La connexion a été interrompue. Veuillez réessayer.", "de" to "Die Verbindung wurde unterbrochen. Bitte versuche es erneut.", "pt" to "A conexão foi interrompida. Tente novamente.", "it" to "La connessione è stata interrotta. Riprova.", "ja" to "接続が中断されました。もう一度お試しください。", "ko" to "연결이 중단되었습니다. 다시 시도해 주세요.", "nl" to "Verbinding werd onderbroken. Probeer het opnieuw.", "zh" to "连接已中断,请重试。"),
|
||||||
|
"err.net.ssl" to mapOf("en" to "Secure connection failed. Please try again.", "es" to "Falló la conexión segura. Inténtalo de nuevo.", "fr" to "Échec de la connexion sécurisée. Veuillez réessayer.", "de" to "Sichere Verbindung fehlgeschlagen. Bitte versuche es erneut.", "pt" to "Falha na conexão segura. Tente novamente.", "it" to "Connessione sicura non riuscita. Riprova.", "ja" to "セキュア接続に失敗しました。もう一度お試しください。", "ko" to "보안 연결에 실패했습니다. 다시 시도해 주세요.", "nl" to "Beveiligde verbinding mislukt. Probeer het opnieuw.", "zh" to "安全连接失败,请重试。"),
|
||||||
|
"err.net.generic" to mapOf("en" to "Connection error. Please try again.", "es" to "Error de conexión. Inténtalo de nuevo.", "fr" to "Erreur de connexion. Veuillez réessayer.", "de" to "Verbindungsfehler. Bitte versuche es erneut.", "pt" to "Erro de conexão. Tente novamente.", "it" to "Errore di connessione. Riprova.", "ja" to "接続エラーです。もう一度お試しください。", "ko" to "연결 오류입니다. 다시 시도해 주세요.", "nl" to "Verbindingsfout. Probeer het opnieuw.", "zh" to "连接出错,请重试。"),
|
||||||
|
"err.too_many_requests" to mapOf("en" to "Too many requests. Please try again later.", "es" to "Demasiadas solicitudes. Inténtalo más tarde.", "fr" to "Trop de requêtes. Réessayez plus tard.", "de" to "Zu viele Anfragen. Bitte versuche es später erneut.", "pt" to "Muitas solicitações. Tente novamente mais tarde.", "it" to "Troppe richieste. Riprova più tardi.", "ja" to "リクエストが多すぎます。しばらくしてからもう一度お試しください。", "ko" to "요청이 너무 많습니다. 잠시 후 다시 시도해 주세요.", "nl" to "Te veel verzoeken. Probeer het later opnieuw.", "zh" to "请求过于频繁,请稍后重试。"),
|
||||||
|
"err.too_many_requests_retry" to mapOf("en" to "Too many requests. Please try again in {0} seconds.", "es" to "Demasiadas solicitudes. Inténtalo de nuevo en {0} segundos.", "fr" to "Trop de requêtes. Réessayez dans {0} secondes.", "de" to "Zu viele Anfragen. Bitte versuche es in {0} Sekunden erneut.", "pt" to "Muitas solicitações. Tente novamente em {0} segundos.", "it" to "Troppe richieste. Riprova tra {0} secondi.", "ja" to "リクエストが多すぎます。{0}秒後にもう一度お試しください。", "ko" to "요청이 너무 많습니다. {0}초 후 다시 시도해 주세요.", "nl" to "Te veel verzoeken. Probeer het over {0} seconden opnieuw.", "zh" to "请求过于频繁,请在{0}秒后重试。"),
|
||||||
|
"err.details" to mapOf("en" to "Details:", "es" to "Detalles:", "fr" to "Détails :", "de" to "Details:", "pt" to "Detalhes:", "it" to "Dettagli:", "ja" to "詳細:", "ko" to "세부 정보:", "nl" to "Details:", "zh" to "详情:"),
|
||||||
|
"err.with_status" to mapOf("en" to "An error occurred ({0})", "es" to "Ocurrió un error ({0})", "fr" to "Une erreur s'est produite ({0})", "de" to "Ein Fehler ist aufgetreten ({0})", "pt" to "Ocorreu um erro ({0})", "it" to "Si è verificato un errore ({0})", "ja" to "エラーが発生しました({0})", "ko" to "오류가 발생했습니다 ({0})", "nl" to "Er is een fout opgetreden ({0})", "zh" to "发生错误({0})"),
|
||||||
|
"upload.too_large" to mapOf("en" to "That photo is too large after resizing.", "es" to "Esa foto es demasiado grande después de redimensionarla.", "fr" to "Cette photo est trop volumineuse après redimensionnement.", "de" to "Dieses Foto ist auch nach dem Verkleinern zu groß.", "pt" to "Essa foto está muito grande após o redimensionamento.", "it" to "La foto è troppo grande dopo il ridimensionamento.", "ja" to "リサイズ後もこの写真は大きすぎます。", "ko" to "크기 조정 후에도 사진이 너무 큽니다.", "nl" to "Die foto is te groot na het verkleinen.", "zh" to "该照片压缩后仍过大。"),
|
||||||
|
"upload.unsupported" to mapOf("en" to "That image format isn't supported.", "es" to "Ese formato de imagen no es compatible.", "fr" to "Ce format d'image n'est pas pris en charge.", "de" to "Dieses Bildformat wird nicht unterstützt.", "pt" to "Esse formato de imagem não é compatível.", "it" to "Questo formato immagine non è supportato.", "ja" to "この画像形式はサポートされていません。", "ko" to "지원되지 않는 이미지 형식입니다.", "nl" to "Dat afbeeldingsformaat wordt niet ondersteund.", "zh" to "不支持该图片格式。"),
|
||||||
|
"upload.too_many" to mapOf("en" to "Too many uploads in flight; try again shortly.", "es" to "Demasiadas subidas en curso; inténtalo de nuevo en un momento.", "fr" to "Trop d'envois en cours ; réessayez sous peu.", "de" to "Zu viele Uploads gleichzeitig. Bitte versuche es gleich erneut.", "pt" to "Muitos envios em andamento; tente novamente em instantes.", "it" to "Troppi caricamenti in corso; riprova tra poco.", "ja" to "アップロード中のファイルが多すぎます。しばらくしてからお試しください。", "ko" to "업로드가 너무 많습니다. 잠시 후 다시 시도해 주세요.", "nl" to "Te veel uploads tegelijk; probeer het zo opnieuw.", "zh" to "上传任务过多,请稍后再试。"),
|
||||||
|
"upload.start_failed" to mapOf("en" to "Couldn't start upload.", "es" to "No se pudo iniciar la subida.", "fr" to "Impossible de démarrer l'envoi.", "de" to "Upload konnte nicht gestartet werden.", "pt" to "Não foi possível iniciar o envio.", "it" to "Impossibile avviare il caricamento.", "ja" to "アップロードを開始できませんでした。", "ko" to "업로드를 시작할 수 없습니다.", "nl" to "Kon upload niet starten.", "zh" to "无法开始上传。"),
|
||||||
|
"upload.storage_failed" to mapOf("en" to "Upload to storage failed.", "es" to "Falló la subida al almacenamiento.", "fr" to "Échec de l'envoi vers le stockage.", "de" to "Upload zum Speicher fehlgeschlagen.", "pt" to "Falha no envio para o armazenamento.", "it" to "Caricamento nello spazio di archiviazione non riuscito.", "ja" to "ストレージへのアップロードに失敗しました。", "ko" to "저장소 업로드에 실패했습니다.", "nl" to "Uploaden naar opslag mislukt.", "zh" to "上传至存储失败。"),
|
||||||
|
"upload.net_presign" to mapOf("en" to "Network error during presign.", "es" to "Error de red durante la autorización.", "fr" to "Erreur réseau lors de la pré-signature.", "de" to "Netzwerkfehler bei der Vorbereitung.", "pt" to "Erro de rede durante a pré-assinatura.", "it" to "Errore di rete durante la pre-autorizzazione.", "ja" to "署名取得中にネットワークエラーが発生しました。", "ko" to "사전 서명 중 네트워크 오류가 발생했습니다.", "nl" to "Netwerkfout tijdens presign.", "zh" to "预签名时网络出错。"),
|
||||||
|
"upload.net_upload" to mapOf("en" to "Network error during upload.", "es" to "Error de red durante la subida.", "fr" to "Erreur réseau lors de l'envoi.", "de" to "Netzwerkfehler beim Upload.", "pt" to "Erro de rede durante o envio.", "it" to "Errore di rete durante il caricamento.", "ja" to "アップロード中にネットワークエラーが発生しました。", "ko" to "업로드 중 네트워크 오류가 발생했습니다.", "nl" to "Netwerkfout tijdens uploaden.", "zh" to "上传时网络出错。"),
|
||||||
|
"upload.failed" to mapOf("en" to "Upload failed.", "es" to "La subida falló.", "fr" to "Échec de l'envoi.", "de" to "Upload fehlgeschlagen.", "pt" to "Falha no envio.", "it" to "Caricamento non riuscito.", "ja" to "アップロードに失敗しました。", "ko" to "업로드에 실패했습니다.", "nl" to "Uploaden mislukt.", "zh" to "上传失败。"),
|
||||||
|
"task.frequency.one_time" to mapOf("en" to "One time", "es" to "Una vez", "fr" to "Une fois", "de" to "Einmalig", "pt" to "Uma vez", "it" to "Una volta", "ja" to "1回のみ", "ko" to "한 번", "nl" to "Eenmalig", "zh" to "一次性"),
|
||||||
|
"task.category.uncategorized" to mapOf("en" to "Uncategorized", "es" to "Sin categoría", "fr" to "Non classé", "de" to "Ohne Kategorie", "pt" to "Sem categoria", "it" to "Senza categoria", "ja" to "未分類", "ko" to "분류 없음", "nl" to "Niet ingedeeld", "zh" to "未分类"),
|
||||||
|
"err.api.failed_fetch_completions" to mapOf("en" to "Failed to fetch completions", "es" to "No se pudieron obtener las finalizaciones", "fr" to "Échec de la récupération des achèvements", "de" to "Erledigungen konnten nicht abgerufen werden", "pt" to "Falha ao buscar as conclusões", "it" to "Impossibile recuperare i completamenti", "ja" to "完了履歴の取得に失敗しました", "ko" to "완료 내역을 가져오지 못했습니다", "nl" to "Voltooiingen ophalen mislukt", "zh" to "获取完成记录失败"),
|
||||||
|
"err.api.failed_fetch_completion" to mapOf("en" to "Failed to fetch completion", "es" to "No se pudo obtener la finalización", "fr" to "Échec de la récupération de l'achèvement", "de" to "Erledigung konnte nicht abgerufen werden", "pt" to "Falha ao buscar a conclusão", "it" to "Impossibile recuperare il completamento", "ja" to "完了履歴の取得に失敗しました", "ko" to "완료 내역을 가져오지 못했습니다", "nl" to "Voltooiing ophalen mislukt", "zh" to "获取完成记录失败"),
|
||||||
|
"err.api.failed_create_completion" to mapOf("en" to "Failed to create completion", "es" to "No se pudo crear la finalización", "fr" to "Échec de la création de l'achèvement", "de" to "Erledigung konnte nicht erstellt werden", "pt" to "Falha ao criar a conclusão", "it" to "Impossibile creare il completamento", "ja" to "完了の作成に失敗しました", "ko" to "완료 내역을 생성하지 못했습니다", "nl" to "Voltooiing aanmaken mislukt", "zh" to "创建完成记录失败"),
|
||||||
|
"err.api.failed_update_completion" to mapOf("en" to "Failed to update completion", "es" to "No se pudo actualizar la finalización", "fr" to "Échec de la mise à jour de l'achèvement", "de" to "Erledigung konnte nicht aktualisiert werden", "pt" to "Falha ao atualizar a conclusão", "it" to "Impossibile aggiornare il completamento", "ja" to "完了の更新に失敗しました", "ko" to "완료 내역을 업데이트하지 못했습니다", "nl" to "Voltooiing bijwerken mislukt", "zh" to "更新完成记录失败"),
|
||||||
|
"err.api.failed_delete_completion" to mapOf("en" to "Failed to delete completion", "es" to "No se pudo eliminar la finalización", "fr" to "Échec de la suppression de l'achèvement", "de" to "Erledigung konnte nicht gelöscht werden", "pt" to "Falha ao excluir a conclusão", "it" to "Impossibile eliminare il completamento", "ja" to "完了の削除に失敗しました", "ko" to "완료 내역을 삭제하지 못했습니다", "nl" to "Voltooiing verwijderen mislukt", "zh" to "删除完成记录失败"),
|
||||||
|
"err.api.failed_fetch_residence_types" to mapOf("en" to "Failed to fetch residence types", "es" to "No se pudieron obtener los tipos de residencia", "fr" to "Échec de la récupération des types de résidence", "de" to "Objekttypen konnten nicht abgerufen werden", "pt" to "Falha ao buscar os tipos de imóvel", "it" to "Impossibile recuperare i tipi di residenza", "ja" to "住居タイプの取得に失敗しました", "ko" to "주거 유형을 가져오지 못했습니다", "nl" to "Woningtypen ophalen mislukt", "zh" to "获取住宅类型失败"),
|
||||||
|
"err.api.failed_fetch_task_frequencies" to mapOf("en" to "Failed to fetch task frequencies", "es" to "No se pudieron obtener las frecuencias de tareas", "fr" to "Échec de la récupération des fréquences de t—ches", "de" to "Aufgabenhäufigkeiten konnten nicht abgerufen werden", "pt" to "Falha ao buscar as frequências de tarefa", "it" to "Impossibile recuperare le frequenze delle attività", "ja" to "タスク頻度の取得に失敗しました", "ko" to "작업 주기를 가져오지 못했습니다", "nl" to "Taakfrequenties ophalen mislukt", "zh" to "获取任务频率失败"),
|
||||||
|
"err.api.failed_fetch_task_priorities" to mapOf("en" to "Failed to fetch task priorities", "es" to "No se pudieron obtener las prioridades de tareas", "fr" to "Échec de la récupération des priorités de t—ches", "de" to "Aufgabenprioritäten konnten nicht abgerufen werden", "pt" to "Falha ao buscar as prioridades de tarefa", "it" to "Impossibile recuperare le priorità delle attività", "ja" to "タスク優先度の取得に失敗しました", "ko" to "작업 우선순위를 가져오지 못했습니다", "nl" to "Taakprioriteiten ophalen mislukt", "zh" to "获取任务优先级失败"),
|
||||||
|
"err.api.failed_fetch_task_categories" to mapOf("en" to "Failed to fetch task categories", "es" to "No se pudieron obtener las categorías de tareas", "fr" to "Échec de la récupération des catégories de t—ches", "de" to "Aufgabenkategorien konnten nicht abgerufen werden", "pt" to "Falha ao buscar as categorias de tarefa", "it" to "Impossibile recuperare le categorie delle attività", "ja" to "タスクカテゴリの取得に失敗しました", "ko" to "작업 카테고리를 가져오지 못했습니다", "nl" to "Taakcategorieën ophalen mislukt", "zh" to "获取任务分类失败"),
|
||||||
|
"err.api.failed_fetch_contractor_specialties" to mapOf("en" to "Failed to fetch contractor specialties", "es" to "No se pudieron obtener las especialidades de contratistas", "fr" to "Échec de la récupération des spécialités des prestataires", "de" to "Handwerker-Fachgebiete konnten nicht abgerufen werden", "pt" to "Falha ao buscar as especialidades dos prestadores", "it" to "Impossibile recuperare le specializzazioni dei tecnici", "ja" to "業者の専門分野の取得に失敗しました", "ko" to "업체 전문 분야를 가져오지 못했습니다", "nl" to "Specialismen van vakmensen ophalen mislukt", "zh" to "获取承包商专长失败"),
|
||||||
|
"err.api.failed_fetch_static_data" to mapOf("en" to "Failed to fetch static data", "es" to "No se pudieron obtener los datos estáticos", "fr" to "Échec de la récupération des données statiques", "de" to "Statische Daten konnten nicht abgerufen werden", "pt" to "Falha ao buscar os dados estáticos", "it" to "Impossibile recuperare i dati statici", "ja" to "静的データの取得に失敗しました", "ko" to "정적 데이터를 가져오지 못했습니다", "nl" to "Statische gegevens ophalen mislukt", "zh" to "获取静态数据失败"),
|
||||||
|
"err.api.failed_fetch_seeded_data" to mapOf("en" to "Failed to fetch seeded data", "es" to "No se pudieron obtener los datos iniciales", "fr" to "Échec de la récupération des données initiales", "de" to "Vorinitialisierte Daten konnten nicht abgerufen werden", "pt" to "Falha ao buscar os dados iniciais", "it" to "Impossibile recuperare i dati iniziali", "ja" to "初期データの取得に失敗しました", "ko" to "초기 데이터를 가져오지 못했습니다", "nl" to "Vooraf ingevulde gegevens ophalen mislukt", "zh" to "获取初始数据失败"),
|
||||||
|
"err.api.failed_fetch_task_templates" to mapOf("en" to "Failed to fetch task templates", "es" to "No se pudieron obtener las plantillas de tareas", "fr" to "Échec de la récupération des modèles de t—ches", "de" to "Aufgabenvorlagen konnten nicht abgerufen werden", "pt" to "Falha ao buscar os modelos de tarefa", "it" to "Impossibile recuperare i modelli di attività", "ja" to "タスクテンプレートの取得に失敗しました", "ko" to "작업 템플릿을 가져오지 못했습니다", "nl" to "Taaksjablonen ophalen mislukt", "zh" to "获取任务模板失败"),
|
||||||
|
"err.api.failed_fetch_grouped_task_templates" to mapOf("en" to "Failed to fetch grouped task templates", "es" to "No se pudieron obtener las plantillas de tareas agrupadas", "fr" to "Échec de la récupération des modèles de t—ches groupés", "de" to "Gruppierte Aufgabenvorlagen konnten nicht abgerufen werden", "pt" to "Falha ao buscar os modelos de tarefa agrupados", "it" to "Impossibile recuperare i modelli di attività raggruppati", "ja" to "グループ化されたタスクテンプレートの取得に失敗しました", "ko" to "그룹화된 작업 템플릿을 가져오지 못했습니다", "nl" to "Gegroepeerde taaksjablonen ophalen mislukt", "zh" to "获取分组任务模板失败"),
|
||||||
|
"err.api.failed_search_task_templates" to mapOf("en" to "Failed to search task templates", "es" to "No se pudieron buscar las plantillas de tareas", "fr" to "Échec de la recherche des modèles de t—ches", "de" to "Aufgabenvorlagen konnten nicht durchsucht werden", "pt" to "Falha ao pesquisar os modelos de tarefa", "it" to "Impossibile cercare i modelli di attività", "ja" to "タスクテンプレートの検索に失敗しました", "ko" to "작업 템플릿을 검색하지 못했습니다", "nl" to "Zoeken naar taaksjablonen mislukt", "zh" to "搜索任务模板失败"),
|
||||||
|
"err.api.failed_fetch_templates_by_category" to mapOf("en" to "Failed to fetch templates by category", "es" to "No se pudieron obtener las plantillas por categoría", "fr" to "Échec de la récupération des modèles par catégorie", "de" to "Vorlagen nach Kategorie konnten nicht abgerufen werden", "pt" to "Falha ao buscar os modelos por categoria", "it" to "Impossibile recuperare i modelli per categoria", "ja" to "カテゴリ別テンプレートの取得に失敗しました", "ko" to "카테고리별 템플릿을 가져오지 못했습니다", "nl" to "Sjablonen per categorie ophalen mislukt", "zh" to "按分类获取模板失败"),
|
||||||
|
"err.api.failed_fetch_task_suggestions" to mapOf("en" to "Failed to fetch task suggestions", "es" to "No se pudieron obtener las sugerencias de tareas", "fr" to "Échec de la récupération des suggestions de t—ches", "de" to "Aufgabenvorschläge konnten nicht abgerufen werden", "pt" to "Falha ao buscar as sugestões de tarefa", "it" to "Impossibile recuperare i suggerimenti di attività", "ja" to "タスクの提案の取得に失敗しました", "ko" to "작업 추천을 가져오지 못했습니다", "nl" to "Taaksuggesties ophalen mislukt", "zh" to "获取任务建议失败"),
|
||||||
|
"err.api.template_not_found" to mapOf("en" to "Template not found", "es" to "Plantilla no encontrada", "fr" to "Modèle introuvable", "de" to "Vorlage nicht gefunden", "pt" to "Modelo não encontrado", "it" to "Modello non trovato", "ja" to "テンプレートが見つかりません", "ko" to "템플릿을 찾을 수 없습니다", "nl" to "Sjabloon niet gevonden", "zh" to "未找到模板"),
|
||||||
|
"err.api.failed_fetch_residences" to mapOf("en" to "Failed to fetch residences", "es" to "No se pudieron obtener las residencias", "fr" to "Échec de la récupération des résidences", "de" to "Objekte konnten nicht abgerufen werden", "pt" to "Falha ao buscar os imóveis", "it" to "Impossibile recuperare le residenze", "ja" to "住居の取得に失敗しました", "ko" to "주거지를 가져오지 못했습니다", "nl" to "Woningen ophalen mislukt", "zh" to "获取住宅失败"),
|
||||||
|
"err.api.failed_fetch_residence" to mapOf("en" to "Failed to fetch residence", "es" to "No se pudo obtener la residencia", "fr" to "Échec de la récupération de la résidence", "de" to "Objekt konnte nicht abgerufen werden", "pt" to "Falha ao buscar o imóvel", "it" to "Impossibile recuperare la residenza", "ja" to "住居の取得に失敗しました", "ko" to "주거지를 가져오지 못했습니다", "nl" to "Woning ophalen mislukt", "zh" to "获取住宅失败"),
|
||||||
|
"err.api.failed_create_residence" to mapOf("en" to "Failed to create residence", "es" to "No se pudo crear la residencia", "fr" to "Échec de la création de la résidence", "de" to "Objekt konnte nicht erstellt werden", "pt" to "Falha ao criar o imóvel", "it" to "Impossibile creare la residenza", "ja" to "住居の作成に失敗しました", "ko" to "주거지를 생성하지 못했습니다", "nl" to "Woning aanmaken mislukt", "zh" to "创建住宅失败"),
|
||||||
|
"err.api.failed_update_residence" to mapOf("en" to "Failed to update residence", "es" to "No se pudo actualizar la residencia", "fr" to "Échec de la mise à jour de la résidence", "de" to "Objekt konnte nicht aktualisiert werden", "pt" to "Falha ao atualizar o imóvel", "it" to "Impossibile aggiornare la residenza", "ja" to "住居の更新に失敗しました", "ko" to "주거지를 업데이트하지 못했습니다", "nl" to "Woning bijwerken mislukt", "zh" to "更新住宅失败"),
|
||||||
|
"err.api.failed_delete_residence" to mapOf("en" to "Failed to delete residence", "es" to "No se pudo eliminar la residencia", "fr" to "Échec de la suppression de la résidence", "de" to "Objekt konnte nicht gelöscht werden", "pt" to "Falha ao excluir o imóvel", "it" to "Impossibile eliminare la residenza", "ja" to "住居の削除に失敗しました", "ko" to "주거지를 삭제하지 못했습니다", "nl" to "Woning verwijderen mislukt", "zh" to "删除住宅失败"),
|
||||||
|
"err.api.failed_fetch_summary" to mapOf("en" to "Failed to fetch summary", "es" to "No se pudo obtener el resumen", "fr" to "Échec de la récupération du résumé", "de" to "Übersicht konnte nicht abgerufen werden", "pt" to "Falha ao buscar o resumo", "it" to "Impossibile recuperare il riepilogo", "ja" to "サマリーの取得に失敗しました", "ko" to "요약을 가져오지 못했습니다", "nl" to "Overzicht ophalen mislukt", "zh" to "获取摘要失败"),
|
||||||
|
"err.api.failed_fetch_my_residences" to mapOf("en" to "Failed to fetch my residences", "es" to "No se pudieron obtener mis residencias", "fr" to "Échec de la récupération de mes résidences", "de" to "Meine Objekte konnten nicht abgerufen werden", "pt" to "Falha ao buscar os meus imóveis", "it" to "Impossibile recuperare le mie residenze", "ja" to "自分の住居の取得に失敗しました", "ko" to "내 주거지를 가져오지 못했습니다", "nl" to "Mijn woningen ophalen mislukt", "zh" to "获取我的住宅失败"),
|
||||||
|
"err.api.failed_fetch_subscription_status" to mapOf("en" to "Failed to fetch subscription status", "es" to "No se pudo obtener el estado de la suscripción", "fr" to "Échec de la récupération du statut de l'abonnement", "de" to "Abo-Status konnte nicht abgerufen werden", "pt" to "Falha ao buscar o status da assinatura", "it" to "Impossibile recuperare lo stato dell'abbonamento", "ja" to "サブスクリプション状況の取得に失敗しました", "ko" to "구독 상태를 가져오지 못했습니다", "nl" to "Abonnementsstatus ophalen mislukt", "zh" to "获取订阅状态失败"),
|
||||||
|
"err.api.failed_fetch_upgrade_triggers" to mapOf("en" to "Failed to fetch upgrade triggers", "es" to "No se pudieron obtener los activadores de mejora", "fr" to "Échec de la récupération des déclencheurs de mise à niveau", "de" to "Upgrade-Auslöser konnten nicht abgerufen werden", "pt" to "Falha ao buscar os gatilhos de upgrade", "it" to "Impossibile recuperare i trigger di upgrade", "ja" to "アップグレードトリガーの取得に失敗しました", "ko" to "업그레이드 조건을 가져오지 못했습니다", "nl" to "Upgrade-triggers ophalen mislukt", "zh" to "获取升级触发条件失败"),
|
||||||
|
"err.api.failed_fetch_feature_benefits" to mapOf("en" to "Failed to fetch feature benefits", "es" to "No se pudieron obtener los beneficios de las funciones", "fr" to "Échec de la récupération des avantages des fonctionnalités", "de" to "Funktionsvorteile konnten nicht abgerufen werden", "pt" to "Falha ao buscar os benefícios dos recursos", "it" to "Impossibile recuperare i vantaggi delle funzioni", "ja" to "機能の特典の取得に失敗しました", "ko" to "기능 혜택을 가져오지 못했습니다", "nl" to "Functievoordelen ophalen mislukt", "zh" to "获取功能权益失败"),
|
||||||
|
"err.api.failed_fetch_promotions" to mapOf("en" to "Failed to fetch promotions", "es" to "No se pudieron obtener las promociones", "fr" to "Échec de la récupération des promotions", "de" to "Aktionen konnten nicht abgerufen werden", "pt" to "Falha ao buscar as promoções", "it" to "Impossibile recuperare le promozioni", "ja" to "プロモーションの取得に失敗しました", "ko" to "프로모션을 가져오지 못했습니다", "nl" to "Promoties ophalen mislukt", "zh" to "获取促销活动失败"),
|
||||||
|
"err.api.failed_verify_ios_receipt" to mapOf("en" to "Failed to verify iOS receipt", "es" to "No se pudo verificar el recibo de iOS", "fr" to "Échec de la vérification du reçu iOS", "de" to "iOS-Beleg konnte nicht überprüft werden", "pt" to "Falha ao verificar o recibo do iOS", "it" to "Impossibile verificare la ricevuta iOS", "ja" to "iOSレシートの確認に失敗しました", "ko" to "iOS 영수증을 확인하지 못했습니다", "nl" to "iOS-bon verifiëren mislukt", "zh" to "验证 iOS 收据失败"),
|
||||||
|
"err.api.failed_verify_android_purchase" to mapOf("en" to "Failed to verify Android purchase", "es" to "No se pudo verificar la compra de Android", "fr" to "Échec de la vérification de l'achat Android", "de" to "Android-Kauf konnte nicht überprüft werden", "pt" to "Falha ao verificar a compra do Android", "it" to "Impossibile verificare l'acquisto Android", "ja" to "Androidの購入の確認に失敗しました", "ko" to "Android 구매를 확인하지 못했습니다", "nl" to "Android-aankoop verifiëren mislukt", "zh" to "验证 Android 购买失败"),
|
||||||
|
"err.api.failed_restore_subscription" to mapOf("en" to "Failed to restore subscription", "es" to "No se pudo restaurar la suscripción", "fr" to "Échec de la restauration de l'abonnement", "de" to "Abo konnte nicht wiederhergestellt werden", "pt" to "Falha ao restaurar a assinatura", "it" to "Impossibile ripristinare l'abbonamento", "ja" to "サブスクリプションの復元に失敗しました", "ko" to "구독을 복원하지 못했습니다", "nl" to "Abonnement herstellen mislukt", "zh" to "恢复订阅失败"),
|
||||||
|
"err.api.failed_fetch_contractors" to mapOf("en" to "Failed to fetch contractors", "es" to "No se pudieron obtener los contratistas", "fr" to "Échec de la récupération des prestataires", "de" to "Handwerker konnten nicht abgerufen werden", "pt" to "Falha ao buscar os prestadores", "it" to "Impossibile recuperare i tecnici", "ja" to "業者の取得に失敗しました", "ko" to "업체를 가져오지 못했습니다", "nl" to "Vakmensen ophalen mislukt", "zh" to "获取承包商失败"),
|
||||||
|
"err.api.failed_fetch_contractor" to mapOf("en" to "Failed to fetch contractor", "es" to "No se pudo obtener el contratista", "fr" to "Échec de la récupération du prestataire", "de" to "Handwerker konnte nicht abgerufen werden", "pt" to "Falha ao buscar o prestador", "it" to "Impossibile recuperare il tecnico", "ja" to "業者の取得に失敗しました", "ko" to "업체를 가져오지 못했습니다", "nl" to "Vakman ophalen mislukt", "zh" to "获取承包商失败"),
|
||||||
|
"err.api.failed_create_contractor" to mapOf("en" to "Failed to create contractor", "es" to "No se pudo crear el contratista", "fr" to "Échec de la création du prestataire", "de" to "Handwerker konnte nicht erstellt werden", "pt" to "Falha ao criar o prestador", "it" to "Impossibile creare il tecnico", "ja" to "業者の作成に失敗しました", "ko" to "업체를 생성하지 못했습니다", "nl" to "Vakman aanmaken mislukt", "zh" to "创建承包商失败"),
|
||||||
|
"err.api.failed_update_contractor" to mapOf("en" to "Failed to update contractor", "es" to "No se pudo actualizar el contratista", "fr" to "Échec de la mise à jour du prestataire", "de" to "Handwerker konnte nicht aktualisiert werden", "pt" to "Falha ao atualizar o prestador", "it" to "Impossibile aggiornare il tecnico", "ja" to "業者の更新に失敗しました", "ko" to "업체를 업데이트하지 못했습니다", "nl" to "Vakman bijwerken mislukt", "zh" to "更新承包商失败"),
|
||||||
|
"err.api.failed_delete_contractor" to mapOf("en" to "Failed to delete contractor", "es" to "No se pudo eliminar el contratista", "fr" to "Échec de la suppression du prestataire", "de" to "Handwerker konnte nicht gelöscht werden", "pt" to "Falha ao excluir o prestador", "it" to "Impossibile eliminare il tecnico", "ja" to "業者の削除に失敗しました", "ko" to "업체를 삭제하지 못했습니다", "nl" to "Vakman verwijderen mislukt", "zh" to "删除承包商失败"),
|
||||||
|
"err.api.failed_toggle_favorite" to mapOf("en" to "Failed to toggle favorite", "es" to "No se pudo cambiar el favorito", "fr" to "Échec de la modification du favori", "de" to "Favorit konnte nicht umgeschaltet werden", "pt" to "Falha ao alternar favorito", "it" to "Impossibile aggiornare i preferiti", "ja" to "お気に入りの切り替えに失敗しました", "ko" to "즐겨찾기를 변경하지 못했습니다", "nl" to "Favoriet wijzigen mislukt", "zh" to "切换收藏失败"),
|
||||||
|
"err.api.failed_fetch_contractor_tasks" to mapOf("en" to "Failed to fetch contractor tasks", "es" to "No se pudieron obtener las tareas del contratista", "fr" to "Échec de la récupération des t—ches du prestataire", "de" to "Handwerker-Aufgaben konnten nicht abgerufen werden", "pt" to "Falha ao buscar as tarefas do prestador", "it" to "Impossibile recuperare le attività del tecnico", "ja" to "業者のタスクの取得に失敗しました", "ko" to "업체 작업을 가져오지 못했습니다", "nl" to "Taken van vakman ophalen mislukt", "zh" to "获取承包商任务失败"),
|
||||||
|
"err.api.failed_fetch_contractors_for_residence" to mapOf("en" to "Failed to fetch contractors for residence", "es" to "No se pudieron obtener los contratistas de la residencia", "fr" to "Échec de la récupération des prestataires pour la résidence", "de" to "Handwerker für das Objekt konnten nicht abgerufen werden", "pt" to "Falha ao buscar os prestadores do imóvel", "it" to "Impossibile recuperare i tecnici per la residenza", "ja" to "住居の業者の取得に失敗しました", "ko" to "주거지의 업체를 가져오지 못했습니다", "nl" to "Vakmensen voor woning ophalen mislukt", "zh" to "获取该住宅的承包商失败"),
|
||||||
|
"err.api.failed_fetch_documents" to mapOf("en" to "Failed to fetch documents", "es" to "No se pudieron obtener los documentos", "fr" to "Échec de la récupération des documents", "de" to "Dokumente konnten nicht abgerufen werden", "pt" to "Falha ao buscar os documentos", "it" to "Impossibile recuperare i documenti", "ja" to "ドキュメントの取得に失敗しました", "ko" to "문서를 가져오지 못했습니다", "nl" to "Documenten ophalen mislukt", "zh" to "获取文档失败"),
|
||||||
|
"err.api.failed_fetch_document" to mapOf("en" to "Failed to fetch document", "es" to "No se pudo obtener el documento", "fr" to "Échec de la récupération du document", "de" to "Dokument konnte nicht abgerufen werden", "pt" to "Falha ao buscar o documento", "it" to "Impossibile recuperare il documento", "ja" to "ドキュメントの取得に失敗しました", "ko" to "문서를 가져오지 못했습니다", "nl" to "Document ophalen mislukt", "zh" to "获取文档失败"),
|
||||||
|
"err.api.failed_create_document" to mapOf("en" to "Failed to create document", "es" to "No se pudo crear el documento", "fr" to "Échec de la création du document", "de" to "Dokument konnte nicht erstellt werden", "pt" to "Falha ao criar o documento", "it" to "Impossibile creare il documento", "ja" to "ドキュメントの作成に失敗しました", "ko" to "문서를 생성하지 못했습니다", "nl" to "Document aanmaken mislukt", "zh" to "创建文档失败"),
|
||||||
|
"err.api.failed_update_document" to mapOf("en" to "Failed to update document", "es" to "No se pudo actualizar el documento", "fr" to "Échec de la mise à jour du document", "de" to "Dokument konnte nicht aktualisiert werden", "pt" to "Falha ao atualizar o documento", "it" to "Impossibile aggiornare il documento", "ja" to "ドキュメントの更新に失敗しました", "ko" to "문서를 업데이트하지 못했습니다", "nl" to "Document bijwerken mislukt", "zh" to "更新文档失败"),
|
||||||
|
"err.api.failed_delete_document" to mapOf("en" to "Failed to delete document", "es" to "No se pudo eliminar el documento", "fr" to "Échec de la suppression du document", "de" to "Dokument konnte nicht gelöscht werden", "pt" to "Falha ao excluir o documento", "it" to "Impossibile eliminare il documento", "ja" to "ドキュメントの削除に失敗しました", "ko" to "문서를 삭제하지 못했습니다", "nl" to "Document verwijderen mislukt", "zh" to "删除文档失败"),
|
||||||
|
"err.api.failed_download_document" to mapOf("en" to "Failed to download document", "es" to "No se pudo descargar el documento", "fr" to "Échec du téléchargement du document", "de" to "Dokument konnte nicht heruntergeladen werden", "pt" to "Falha ao baixar o documento", "it" to "Impossibile scaricare il documento", "ja" to "ドキュメントのダウンロードに失敗しました", "ko" to "문서를 다운로드하지 못했습니다", "nl" to "Document downloaden mislukt", "zh" to "下载文档失败"),
|
||||||
|
"err.api.failed_activate_document" to mapOf("en" to "Failed to activate document", "es" to "No se pudo activar el documento", "fr" to "Échec de l'activation du document", "de" to "Dokument konnte nicht aktiviert werden", "pt" to "Falha ao ativar o documento", "it" to "Impossibile attivare il documento", "ja" to "ドキュメントの有効化に失敗しました", "ko" to "문서를 활성화하지 못했습니다", "nl" to "Document activeren mislukt", "zh" to "激活文档失败"),
|
||||||
|
"err.api.failed_deactivate_document" to mapOf("en" to "Failed to deactivate document", "es" to "No se pudo desactivar el documento", "fr" to "Échec de la désactivation du document", "de" to "Dokument konnte nicht deaktiviert werden", "pt" to "Falha ao desativar o documento", "it" to "Impossibile disattivare il documento", "ja" to "ドキュメントの無効化に失敗しました", "ko" to "문서를 비활성화하지 못했습니다", "nl" to "Document deactiveren mislukt", "zh" to "停用文档失败"),
|
||||||
|
"err.api.failed_upload_document_image" to mapOf("en" to "Failed to upload document image", "es" to "No se pudo subir la imagen del documento", "fr" to "Échec de l'envoi de l'image du document", "de" to "Dokumentbild konnte nicht hochgeladen werden", "pt" to "Falha ao enviar a imagem do documento", "it" to "Impossibile caricare l'immagine del documento", "ja" to "ドキュメント画像のアップロードに失敗しました", "ko" to "문서 이미지를 업로드하지 못했습니다", "nl" to "Documentafbeelding uploaden mislukt", "zh" to "上传文档图片失败"),
|
||||||
|
"err.api.failed_delete_document_image" to mapOf("en" to "Failed to delete document image", "es" to "No se pudo eliminar la imagen del documento", "fr" to "Échec de la suppression de l'image du document", "de" to "Dokumentbild konnte nicht gelöscht werden", "pt" to "Falha ao excluir a imagem do documento", "it" to "Impossibile eliminare l'immagine del documento", "ja" to "ドキュメント画像の削除に失敗しました", "ko" to "문서 이미지를 삭제하지 못했습니다", "nl" to "Documentafbeelding verwijderen mislukt", "zh" to "删除文档图片失败"),
|
||||||
|
"err.api.device_registration_failed" to mapOf("en" to "Device registration failed", "es" to "Falló el registro del dispositivo", "fr" to "Échec de l'enregistrement de l'appareil", "de" to "Geräteregistrierung fehlgeschlagen", "pt" to "Falha no registro do dispositivo", "it" to "Registrazione del dispositivo non riuscita", "ja" to "デバイスの登録に失敗しました", "ko" to "기기 등록에 실패했습니다", "nl" to "Apparaatregistratie mislukt", "zh" to "设备注册失败"),
|
||||||
|
"err.api.device_unregistration_failed" to mapOf("en" to "Device unregistration failed", "es" to "Falló la cancelación del registro del dispositivo", "fr" to "Échec de la désinscription de l'appareil", "de" to "Geräteabmeldung fehlgeschlagen", "pt" to "Falha ao cancelar o registro do dispositivo", "it" to "Annullamento registrazione del dispositivo non riuscito", "ja" to "デバイスの登録解除に失敗しました", "ko" to "기기 등록 해제에 실패했습니다", "nl" to "Apparaatregistratie ongedaan maken mislukt", "zh" to "设备注销失败"),
|
||||||
|
"err.api.failed_get_preferences" to mapOf("en" to "Failed to get preferences", "es" to "No se pudieron obtener las preferencias", "fr" to "Échec de la récupération des préférences", "de" to "Einstellungen konnten nicht abgerufen werden", "pt" to "Falha ao obter as preferências", "it" to "Impossibile recuperare le preferenze", "ja" to "設定の取得に失敗しました", "ko" to "환경설정을 가져오지 못했습니다", "nl" to "Voorkeuren ophalen mislukt", "zh" to "获取偏好设置失败"),
|
||||||
|
"err.api.failed_update_preferences" to mapOf("en" to "Failed to update preferences", "es" to "No se pudieron actualizar las preferencias", "fr" to "Échec de la mise à jour des préférences", "de" to "Einstellungen konnten nicht aktualisiert werden", "pt" to "Falha ao atualizar as preferências", "it" to "Impossibile aggiornare le preferenze", "ja" to "設定の更新に失敗しました", "ko" to "환경설정을 업데이트하지 못했습니다", "nl" to "Voorkeuren bijwerken mislukt", "zh" to "更新偏好设置失败"),
|
||||||
|
"err.api.failed_get_notification_history" to mapOf("en" to "Failed to get notification history", "es" to "No se pudo obtener el historial de notificaciones", "fr" to "Échec de la récupération de l'historique des notifications", "de" to "Benachrichtigungsverlauf konnte nicht abgerufen werden", "pt" to "Falha ao obter o histórico de notificações", "it" to "Impossibile recuperare la cronologia delle notifiche", "ja" to "通知履歴の取得に失敗しました", "ko" to "알림 기록을 가져오지 못했습니다", "nl" to "Meldingsgeschiedenis ophalen mislukt", "zh" to "获取通知历史失败"),
|
||||||
|
"err.api.failed_mark_notification_read" to mapOf("en" to "Failed to mark notification as read", "es" to "No se pudo marcar la notificación como leída", "fr" to "Échec du marquage de la notification comme lue", "de" to "Benachrichtigung konnte nicht als gelesen markiert werden", "pt" to "Falha ao marcar a notificação como lida", "it" to "Impossibile contrassegnare la notifica come letta", "ja" to "通知を既読にできませんでした", "ko" to "알림을 읽음으로 표시하지 못했습니다", "nl" to "Melding als gelezen markeren mislukt", "zh" to "标记通知为已读失败"),
|
||||||
|
"err.api.failed_mark_all_notifications_read" to mapOf("en" to "Failed to mark all notifications as read", "es" to "No se pudieron marcar todas las notificaciones como leídas", "fr" to "Échec du marquage de toutes les notifications comme lues", "de" to "Benachrichtigungen konnten nicht alle als gelesen markiert werden", "pt" to "Falha ao marcar todas as notificações como lidas", "it" to "Impossibile contrassegnare tutte le notifiche come lette", "ja" to "すべての通知を既読にできませんでした", "ko" to "모든 알림을 읽음으로 표시하지 못했습니다", "nl" to "Alle meldingen als gelezen markeren mislukt", "zh" to "标记所有通知为已读失败"),
|
||||||
|
"err.api.failed_get_unread_count" to mapOf("en" to "Failed to get unread count", "es" to "No se pudo obtener el número de no leídas", "fr" to "Échec de la récupération du nombre de non lus", "de" to "Anzahl ungelesener Nachrichten konnte nicht abgerufen werden", "pt" to "Falha ao obter a contagem de não lidas", "it" to "Impossibile recuperare il numero di non letti", "ja" to "未読件数の取得に失敗しました", "ko" to "읽지 않은 개수를 가져오지 못했습니다", "nl" to "Aantal ongelezen ophalen mislukt", "zh" to "获取未读数量失败"),
|
||||||
|
"err.api.task_not_found" to mapOf("en" to "Task not found", "es" to "Tarea no encontrada", "fr" to "T—che introuvable", "de" to "Aufgabe nicht gefunden", "pt" to "Tarefa não encontrada", "it" to "Attività non trovata", "ja" to "タスクが見つかりません", "ko" to "작업을 찾을 수 없습니다", "nl" to "Taak niet gevonden", "zh" to "未找到任务"),
|
||||||
|
"err.api.access_denied" to mapOf("en" to "Access denied", "es" to "Acceso denegado", "fr" to "Accès refusé", "de" to "Zugriff verweigert", "pt" to "Acesso negado", "it" to "Accesso negato", "ja" to "アクセスが拒否されました", "ko" to "접근이 거부되었습니다", "nl" to "Toegang geweigerd", "zh" to "访问被拒绝"),
|
||||||
|
"err.api.task_action_failed" to mapOf("en" to "Task {0} failed: {1}", "es" to "La tarea {0} falló: {1}", "fr" to "Échec de la t—che {0} : {1}", "de" to "Aufgabe {0} fehlgeschlagen: {1}", "pt" to "Tarefa {0} falhou: {1}", "it" to "Attività {0} non riuscita: {1}", "ja" to "タスク{0}が失敗しました:{1}", "ko" to "작업 {0} 실패: {1}", "nl" to "Taak {0} mislukt: {1}", "zh" to "任务 {0} 失败:{1}"),
|
||||||
|
"err.api.init_lookups_failed" to mapOf("en" to "Failed to initialize lookups: {0}", "es" to "No se pudieron inicializar las consultas: {0}", "fr" to "Échec de l'initialisation des références : {0}", "de" to "Nachschlagewerte konnten nicht initialisiert werden: {0}", "pt" to "Falha ao inicializar as listas: {0}", "it" to "Impossibile inizializzare le ricerche: {0}", "ja" to "ルックアップの初期化に失敗しました:{0}", "ko" to "조회 항목 초기화 실패: {0}", "nl" to "Opzoekgegevens initialiseren mislukt: {0}", "zh" to "初始化查找数据失败:{0}"),
|
||||||
|
"err.api.load_lookups_failed" to mapOf("en" to "Failed to load lookups: {0}", "es" to "No se pudieron cargar las consultas: {0}", "fr" to "Échec du chargement des références : {0}", "de" to "Nachschlagewerte konnten nicht geladen werden: {0}", "pt" to "Falha ao carregar as listas: {0}", "it" to "Impossibile caricare le ricerche: {0}", "ja" to "ルックアップの読み込みに失敗しました:{0}", "ko" to "조회 항목 로드 실패: {0}", "nl" to "Opzoekgegevens laden mislukt: {0}", "zh" to "加载查找数据失败:{0}"),
|
||||||
|
"err.api.unknown_loading_lookups" to mapOf("en" to "Unknown error loading lookups", "es" to "Error desconocido al cargar las consultas", "fr" to "Erreur inconnue lors du chargement des références", "de" to "Unbekannter Fehler beim Laden der Nachschlagewerte", "pt" to "Erro desconhecido ao carregar as listas", "it" to "Errore sconosciuto durante il caricamento delle ricerche", "ja" to "ルックアップの読み込み中に不明なエラーが発生しました", "ko" to "조회 항목 로드 중 알 수 없는 오류", "nl" to "Onbekende fout bij laden van opzoekgegevens", "zh" to "加载查找数据时发生未知错误"),
|
||||||
|
"err.api.tasks_unavailable" to mapOf("en" to "Tasks unavailable", "es" to "Tareas no disponibles", "fr" to "T—ches indisponibles", "de" to "Aufgaben nicht verfügbar", "pt" to "Tarefas indisponíveis", "it" to "Attività non disponibili", "ja" to "タスクを利用できません", "ko" to "작업을 사용할 수 없습니다", "nl" to "Taken niet beschikbaar", "zh" to "任务不可用"),
|
||||||
|
"err.api.failed_load_task_templates" to mapOf("en" to "Failed to load task templates", "es" to "No se pudieron cargar las plantillas de tareas", "fr" to "Échec du chargement des modèles de t—ches", "de" to "Aufgabenvorlagen konnten nicht geladen werden", "pt" to "Falha ao carregar os modelos de tarefa", "it" to "Impossibile caricare i modelli di attività", "ja" to "タスクテンプレートの読み込みに失敗しました", "ko" to "작업 템플릿을 불러오지 못했습니다", "nl" to "Taaksjablonen laden mislukt", "zh" to "加载任务模板失败"),
|
||||||
|
"err.api.task_template_not_found" to mapOf("en" to "Task template not found", "es" to "Plantilla de tarea no encontrada", "fr" to "Modèle de t—che introuvable", "de" to "Aufgabenvorlage nicht gefunden", "pt" to "Modelo de tarefa não encontrado", "it" to "Modello di attività non trovato", "ja" to "タスクテンプレートが見つかりません", "ko" to "작업 템플릿을 찾을 수 없습니다", "nl" to "Taaksjabloon niet gevonden", "zh" to "未找到任务模板"),
|
||||||
|
"err.api.no_token" to mapOf("en" to "No token", "es" to "Sin token", "fr" to "Aucun jeton", "de" to "Kein Token", "pt" to "Sem token", "it" to "Nessun token", "ja" to "トークンがありません", "ko" to "토큰 없음", "nl" to "Geen token", "zh" to "无令牌"),
|
||||||
|
"err.api.unexpected_state" to mapOf("en" to "Unexpected state", "es" to "Estado inesperado", "fr" to "État inattendu", "de" to "Unerwarteter Zustand", "pt" to "Estado inesperado", "it" to "Stato imprevisto", "ja" to "予期しない状態です", "ko" to "예기치 않은 상태", "nl" to "Onverwachte status", "zh" to "意外状态"),
|
||||||
|
"err.auth.could_not_start_flow" to mapOf("en" to "Could not start {0} (Kratos {1})", "es" to "No se pudo iniciar {0} (Kratos {1})", "fr" to "Impossible de démarrer {0} (Kratos {1})", "de" to "{0} konnte nicht gestartet werden (Kratos {1})", "pt" to "Não foi possível iniciar {0} (Kratos {1})", "it" to "Impossibile avviare {0} (Kratos {1})", "ja" to "{0}を開始できませんでした(Kratos {1})", "ko" to "{0}을(를) 시작할 수 없습니다 (Kratos {1})", "nl" to "Kan {0} niet starten (Kratos {1})", "zh" to "无法启动 {0}(Kratos {1})"),
|
||||||
|
"err.auth.could_not_reach_server" to mapOf("en" to "Could not reach the authentication server", "es" to "No se pudo conectar con el servidor de autenticación", "fr" to "Impossible de joindre le serveur d'authentification", "de" to "Der Authentifizierungsserver ist nicht erreichbar", "pt" to "Não foi possível conectar ao servidor de autenticação", "it" to "Impossibile raggiungere il server di autenticazione", "ja" to "認証サーバーに接続できませんでした", "ko" to "인증 서버에 연결할 수 없습니다", "nl" to "Kan de authenticatieserver niet bereiken", "zh" to "无法连接到身份验证服务器"),
|
||||||
|
"err.auth.request_failed" to mapOf("en" to "Authentication request failed", "es" to "Falló la solicitud de autenticación", "fr" to "Échec de la demande d'authentification", "de" to "Authentifizierungsanfrage fehlgeschlagen", "pt" to "Falha na solicitação de autenticação", "it" to "Richiesta di autenticazione non riuscita", "ja" to "認証リクエストに失敗しました", "ko" to "인증 요청에 실패했습니다", "nl" to "Authenticatieverzoek mislukt", "zh" to "身份验证请求失败"),
|
||||||
|
"err.auth.authentication_failed_status" to mapOf("en" to "Authentication failed ({0})", "es" to "Falló la autenticación ({0})", "fr" to "Échec de l'authentification ({0})", "de" to "Authentifizierung fehlgeschlagen ({0})", "pt" to "Falha na autenticação ({0})", "it" to "Autenticazione non riuscita ({0})", "ja" to "認証に失敗しました({0})", "ko" to "인증 실패 ({0})", "nl" to "Authenticatie mislukt ({0})", "zh" to "身份验证失败({0})"),
|
||||||
|
"err.auth.could_not_start_login" to mapOf("en" to "Could not start login", "es" to "No se pudo iniciar sesión", "fr" to "Impossible de démarrer la connexion", "de" to "Anmeldung konnte nicht gestartet werden", "pt" to "Não foi possível iniciar o login", "it" to "Impossibile avviare l'accesso", "ja" to "ログインを開始できませんでした", "ko" to "로그인을 시작할 수 없습니다", "nl" to "Kan inloggen niet starten", "zh" to "无法开始登录"),
|
||||||
|
"err.auth.login_failed" to mapOf("en" to "Login failed", "es" to "Falló el inicio de sesión", "fr" to "Échec de la connexion", "de" to "Anmeldung fehlgeschlagen", "pt" to "Falha no login", "it" to "Accesso non riuscito", "ja" to "ログインに失敗しました", "ko" to "로그인에 실패했습니다", "nl" to "Inloggen mislukt", "zh" to "登录失败"),
|
||||||
|
"err.auth.registration_failed" to mapOf("en" to "Registration failed", "es" to "Falló el registro", "fr" to "Échec de l'inscription", "de" to "Registrierung fehlgeschlagen", "pt" to "Falha no cadastro", "it" to "Registrazione non riuscita", "ja" to "登録に失敗しました", "ko" to "회원가입에 실패했습니다", "nl" to "Registratie mislukt", "zh" to "注册失败"),
|
||||||
|
"err.auth.could_not_create_account" to mapOf("en" to "Could not create account", "es" to "No se pudo crear la cuenta", "fr" to "Impossible de créer le compte", "de" to "Konto konnte nicht erstellt werden", "pt" to "Não foi possível criar a conta", "it" to "Impossibile creare l'account", "ja" to "アカウントを作成できませんでした", "ko" to "계정을 생성할 수 없습니다", "nl" to "Kan account niet aanmaken", "zh" to "无法创建账户"),
|
||||||
|
"err.auth.could_not_start_signin" to mapOf("en" to "Could not start sign-in", "es" to "No se pudo iniciar la sesión", "fr" to "Impossible de démarrer la connexion", "de" to "Anmeldung konnte nicht gestartet werden", "pt" to "Não foi possível iniciar o login", "it" to "Impossibile avviare l'accesso", "ja" to "サインインを開始できませんでした", "ko" to "로그인을 시작할 수 없습니다", "nl" to "Kan aanmelden niet starten", "zh" to "无法开始登录"),
|
||||||
|
"err.auth.could_not_start_signup" to mapOf("en" to "Could not start sign-up", "es" to "No se pudo iniciar el registro", "fr" to "Impossible de démarrer l'inscription", "de" to "Registrierung konnte nicht gestartet werden", "pt" to "Não foi possível iniciar o cadastro", "it" to "Impossibile avviare la registrazione", "ja" to "サインアップを開始できませんでした", "ko" to "회원가입을 시작할 수 없습니다", "nl" to "Kan registreren niet starten", "zh" to "无法开始注册"),
|
||||||
|
"err.auth.signin_no_session" to mapOf("en" to "Sign-in did not return a session", "es" to "El inicio de sesión no devolvió una sesión", "fr" to "La connexion n'a pas renvoyé de session", "de" to "Bei der Anmeldung wurde keine Sitzung zurückgegeben", "pt" to "O login não retornou uma sessão", "it" to "L'accesso non ha restituito una sessione", "ja" to "サインインでセッションが返されませんでした", "ko" to "로그인 시 세션이 반환되지 않았습니다", "nl" to "Aanmelden heeft geen sessie opgeleverd", "zh" to "登录未返回会话"),
|
||||||
|
"err.auth.signin_failed" to mapOf("en" to "Sign-in failed", "es" to "Falló el inicio de sesión", "fr" to "Échec de la connexion", "de" to "Anmeldung fehlgeschlagen", "pt" to "Falha no login", "it" to "Accesso non riuscito", "ja" to "サインインに失敗しました", "ko" to "로그인에 실패했습니다", "nl" to "Aanmelden mislukt", "zh" to "登录失败"),
|
||||||
|
"err.auth.apple_signin_failed" to mapOf("en" to "Apple Sign In failed", "es" to "Falló Iniciar sesión con Apple", "fr" to "Échec de la connexion avec Apple", "de" to "Apple-Anmeldung fehlgeschlagen", "pt" to "Falha no Login com a Apple", "it" to "Accedi con Apple non riuscito", "ja" to "Appleでのサインインに失敗しました", "ko" to "Apple 로그인에 실패했습니다", "nl" to "Inloggen met Apple mislukt", "zh" to "Apple 登录失败"),
|
||||||
|
"err.auth.google_signin_failed" to mapOf("en" to "Google Sign In failed", "es" to "Falló Iniciar sesión con Google", "fr" to "Échec de la connexion avec Google", "de" to "Google-Anmeldung fehlgeschlagen", "pt" to "Falha no Login com o Google", "it" to "Accedi con Google non riuscito", "ja" to "Googleでのサインインに失敗しました", "ko" to "Google 로그인에 실패했습니다", "nl" to "Inloggen met Google mislukt", "zh" to "Google 登录失败"),
|
||||||
|
"err.auth.could_not_start_recovery" to mapOf("en" to "Could not start password recovery", "es" to "No se pudo iniciar la recuperación de contraseña", "fr" to "Impossible de démarrer la récupération du mot de passe", "de" to "Passwort-Wiederherstellung konnte nicht gestartet werden", "pt" to "Não foi possível iniciar a recuperação de senha", "it" to "Impossibile avviare il recupero della password", "ja" to "パスワードの復旧を開始できませんでした", "ko" to "비밀번호 복구를 시작할 수 없습니다", "nl" to "Kan wachtwoordherstel niet starten", "zh" to "无法开始密码找回"),
|
||||||
|
"err.auth.could_not_send_recovery_code" to mapOf("en" to "Could not send recovery code", "es" to "No se pudo enviar el código de recuperación", "fr" to "Impossible d'envoyer le code de récupération", "de" to "Wiederherstellungscode konnte nicht gesendet werden", "pt" to "Não foi possível enviar o código de recuperação", "it" to "Impossibile inviare il codice di recupero", "ja" to "復旧コードを送信できませんでした", "ko" to "복구 코드를 전송할 수 없습니다", "nl" to "Kan herstelcode niet verzenden", "zh" to "无法发送找回验证码"),
|
||||||
|
"err.auth.recovery_session_expired" to mapOf("en" to "Your recovery session expired. Request a new code.", "es" to "Tu sesión de recuperación expiró. Solicita un nuevo código.", "fr" to "Votre session de récupération a expiré. Demandez un nouveau code.", "de" to "Deine Wiederherstellungssitzung ist abgelaufen. Fordere einen neuen Code an.", "pt" to "Sua sessão de recuperação expirou. Solicite um novo código.", "it" to "La tua sessione di recupero è scaduta. Richiedi un nuovo codice.", "ja" to "復旧セッションの有効期限が切れました。新しいコードをリクエストしてください。", "ko" to "복구 세션이 만료되었습니다. 새 코드를 요청하세요.", "nl" to "Je herstelsessie is verlopen. Vraag een nieuwe code aan.", "zh" to "找回会话已过期,请重新获取验证码。"),
|
||||||
|
"err.auth.invalid_code" to mapOf("en" to "Invalid or expired code", "es" to "Código no válido o expirado", "fr" to "Code non valide ou expiré", "de" to "Ungültiger oder abgelaufener Code", "pt" to "Código inválido ou expirado", "it" to "Codice non valido o scaduto", "ja" to "コードが無効か期限切れです", "ko" to "잘못되었거나 만료된 코드입니다", "nl" to "Ongeldige of verlopen code", "zh" to "验证码无效或已过期"),
|
||||||
|
"err.auth.invalid_code_resend" to mapOf("en" to "Invalid or expired code. Tap resend for a new one.", "es" to "Código no válido o expirado. Toca reenviar para obtener uno nuevo.", "fr" to "Code non valide ou expiré. Touchez Renvoyer pour en obtenir un nouveau.", "de" to "Ungültiger oder abgelaufener Code. Tippe auf Erneut senden für einen neuen.", "pt" to "Código inválido ou expirado. Toque em reenviar para receber um novo.", "it" to "Codice non valido o scaduto. Tocca Invia di nuovo per ottenerne uno nuovo.", "ja" to "コードが無効か期限切れです。再送信をタップして新しいコードを取得してください。", "ko" to "잘못되었거나 만료된 코드입니다. 재전송을 눌러 새 코드를 받으세요.", "nl" to "Ongeldige of verlopen code. Tik op opnieuw verzenden voor een nieuwe.", "zh" to "验证码无效或已过期。点击重新发送以获取新验证码。"),
|
||||||
|
"err.auth.reset_session_expired" to mapOf("en" to "This password reset session has expired. Request a new code.", "es" to "Esta sesión de restablecimiento de contraseña expiró. Solicita un nuevo código.", "fr" to "Cette session de réinitialisation du mot de passe a expiré. Demandez un nouveau code.", "de" to "Diese Passwort-Zurücksetzungssitzung ist abgelaufen. Fordere einen neuen Code an.", "pt" to "Esta sessão de redefinição de senha expirou. Solicite um novo código.", "it" to "Questa sessione di reimpostazione password è scaduta. Richiedi un nuovo codice.", "ja" to "このパスワードリセットセッションは期限切れです。新しいコードをリクエストしてください。", "ko" to "비밀번호 재설정 세션이 만료되었습니다. 새 코드를 요청하세요.", "nl" to "Deze sessie voor wachtwoordherstel is verlopen. Vraag een nieuwe code aan.", "zh" to "此密码重置会话已过期,请重新获取验证码。"),
|
||||||
|
"err.auth.could_not_reset_password" to mapOf("en" to "Could not reset password", "es" to "No se pudo restablecer la contraseña", "fr" to "Impossible de réinitialiser le mot de passe", "de" to "Passwort konnte nicht zurückgesetzt werden", "pt" to "Não foi possível redefinir a senha", "it" to "Impossibile reimpostare la password", "ja" to "パスワードをリセットできませんでした", "ko" to "비밀번호를 재설정할 수 없습니다", "nl" to "Kan wachtwoord niet opnieuw instellen", "zh" to "无法重置密码"),
|
||||||
|
"err.auth.could_not_start_verification" to mapOf("en" to "Could not start verification", "es" to "No se pudo iniciar la verificación", "fr" to "Impossible de démarrer la vérification", "de" to "Verifizierung konnte nicht gestartet werden", "pt" to "Não foi possível iniciar a verificação", "it" to "Impossibile avviare la verifica", "ja" to "認証を開始できませんでした", "ko" to "인증을 시작할 수 없습니다", "nl" to "Kan verificatie niet starten", "zh" to "无法开始验证"),
|
||||||
|
"err.auth.could_not_send_verification_code" to mapOf("en" to "Could not send verification code", "es" to "No se pudo enviar el código de verificación", "fr" to "Impossible d'envoyer le code de vérification", "de" to "Verifizierungscode konnte nicht gesendet werden", "pt" to "Não foi possível enviar o código de verificação", "it" to "Impossibile inviare il codice di verifica", "ja" to "認証コードを送信できませんでした", "ko" to "인증 코드를 전송할 수 없습니다", "nl" to "Kan verificatiecode niet verzenden", "zh" to "无法发送验证码"),
|
||||||
|
"err.auth.request_verification_first" to mapOf("en" to "Please request a verification code first.", "es" to "Solicita primero un código de verificación.", "fr" to "Veuillez d'abord demander un code de vérification.", "de" to "Bitte fordere zuerst einen Verifizierungscode an.", "pt" to "Solicite primeiro um código de verificação.", "it" to "Richiedi prima un codice di verifica.", "ja" to "先に認証コードをリクエストしてください。", "ko" to "먼저 인증 코드를 요청하세요.", "nl" to "Vraag eerst een verificatiecode aan.", "zh" to "请先获取验证码。"),
|
||||||
|
"err.auth.verification_failed" to mapOf("en" to "Verification failed", "es" to "Falló la verificación", "fr" to "Échec de la vérification", "de" to "Verifizierung fehlgeschlagen", "pt" to "Falha na verificação", "it" to "Verifica non riuscita", "ja" to "認証に失敗しました", "ko" to "인증에 실패했습니다", "nl" to "Verificatie mislukt", "zh" to "验证失败"),
|
||||||
|
"err.auth.failed_get_user" to mapOf("en" to "Failed to get user", "es" to "No se pudo obtener el usuario", "fr" to "Échec de la récupération de l'utilisateur", "de" to "Benutzer konnte nicht abgerufen werden", "pt" to "Falha ao obter o usuário", "it" to "Impossibile recuperare l'utente", "ja" to "ユーザー情報の取得に失敗しました", "ko" to "사용자 정보를 가져오지 못했습니다", "nl" to "Gebruiker ophalen mislukt", "zh" to "获取用户失败"),
|
||||||
|
"err.auth.session_expired" to mapOf("en" to "Session expired — please sign in again", "es" to "La sesión expiró — inicia sesión de nuevo", "fr" to "Session expirée — veuillez vous reconnecter", "de" to "Sitzung abgelaufen — bitte melde dich erneut an", "pt" to "Sessão expirada — faça login novamente", "it" to "Sessione scaduta — accedi di nuovo", "ja" to "セッションの有効期限が切れました。もう一度サインインしてください", "ko" to "세션이 만료되었습니다 — 다시 로그인하세요", "nl" to "Sessie verlopen — meld je opnieuw aan", "zh" to "会话已过期 — 请重新登录"),
|
||||||
|
"err.auth.could_not_validate_session" to mapOf("en" to "Could not validate session", "es" to "No se pudo validar la sesión", "fr" to "Impossible de valider la session", "de" to "Sitzung konnte nicht validiert werden", "pt" to "Não foi possível validar a sessão", "it" to "Impossibile convalidare la sessione", "ja" to "セッションを検証できませんでした", "ko" to "세션을 확인할 수 없습니다", "nl" to "Kan sessie niet valideren", "zh" to "无法验证会话"),
|
||||||
|
"err.auth.could_not_load_profile" to mapOf("en" to "Could not load profile after sign-in", "es" to "No se pudo cargar el perfil después de iniciar sesión", "fr" to "Impossible de charger le profil après la connexion", "de" to "Profil konnte nach der Anmeldung nicht geladen werden", "pt" to "Não foi possível carregar o perfil após o login", "it" to "Impossibile caricare il profilo dopo l'accesso", "ja" to "サインイン後にプロフィールを読み込めませんでした", "ko" to "로그인 후 프로필을 불러올 수 없습니다", "nl" to "Kan profiel niet laden na aanmelden", "zh" to "登录后无法加载个人资料"),
|
||||||
|
"err.vm.failed_create_residence" to mapOf("en" to "Failed to create residence", "es" to "No se pudo crear la residencia", "fr" to "Échec de la création de la résidence", "de" to "Objekt konnte nicht erstellt werden", "pt" to "Falha ao criar o imóvel", "it" to "Impossibile creare la residenza", "ja" to "住居の作成に失敗しました", "ko" to "주거지를 생성하지 못했습니다", "nl" to "Woning aanmaken mislukt", "zh" to "创建住宅失败"),
|
||||||
|
"err.vm.failed_join_residence" to mapOf("en" to "Failed to join residence", "es" to "No se pudo unir a la residencia", "fr" to "Échec pour rejoindre la résidence", "de" to "Objekt konnte nicht beigetreten werden", "pt" to "Falha ao entrar no imóvel", "it" to "Impossibile unirsi alla residenza", "ja" to "住居への参加に失敗しました", "ko" to "주거지에 참여하지 못했습니다", "nl" to "Deelnemen aan woning mislukt", "zh" to "加入住宅失败"),
|
||||||
|
"err.vm.upload_unexpected_state" to mapOf("en" to "Upload failed in unexpected state", "es" to "La subida falló en un estado inesperado", "fr" to "Échec de l'envoi dans un état inattendu", "de" to "Upload in unerwartetem Zustand fehlgeschlagen", "pt" to "Falha no envio em estado inesperado", "it" to "Caricamento non riuscito in uno stato imprevisto", "ja" to "予期しない状態でアップロードに失敗しました", "ko" to "예기치 않은 상태로 업로드에 실패했습니다", "nl" to "Upload mislukt in onverwachte status", "zh" to "上传因意外状态失败"),
|
||||||
|
"err.vm.document_updated_image_upload_failed" to mapOf("en" to "Document updated but failed to upload image: {0}", "es" to "Se actualizó el documento pero no se pudo subir la imagen: {0}", "fr" to "Document mis à jour, mais échec de l'envoi de l'image : {0}", "de" to "Dokument aktualisiert, aber Bild-Upload fehlgeschlagen: {0}", "pt" to "Documento atualizado, mas falha ao enviar a imagem: {0}", "it" to "Documento aggiornato ma caricamento dell'immagine non riuscito: {0}", "ja" to "ドキュメントは更新されましたが、画像のアップロードに失敗しました:{0}", "ko" to "문서는 업데이트되었지만 이미지 업로드에 실패했습니다: {0}", "nl" to "Document bijgewerkt, maar afbeelding uploaden mislukt: {0}", "zh" to "文档已更新,但图片上传失败:{0}"),
|
||||||
|
"err.vm.invalid_reset_token" to mapOf("en" to "Invalid reset token. Please start over.", "es" to "Token de restablecimiento no válido. Vuelve a empezar.", "fr" to "Jeton de réinitialisation non valide. Veuillez recommencer.", "de" to "Ungültiges Zurücksetzungs-Token. Bitte beginne von vorn.", "pt" to "Token de redefinição inválido. Comece novamente.", "it" to "Token di reimpostazione non valido. Ricomincia da capo.", "ja" to "リセットトークンが無効です。最初からやり直してください。", "ko" to "잘못된 재설정 토큰입니다. 처음부터 다시 시작하세요.", "nl" to "Ongeldige reset-token. Begin opnieuw.", "zh" to "重置令牌无效,请重新开始。"),
|
||||||
|
"doc.type.warranty" to mapOf("en" to "Warranty", "es" to "Garantía", "fr" to "Garantie", "de" to "Garantie", "pt" to "Garantia", "it" to "Garanzia", "ja" to "保証書", "ko" to "보증서", "nl" to "Garantie", "zh" to "保修单"),
|
||||||
|
"doc.type.manual" to mapOf("en" to "User Manual", "es" to "Manual de usuario", "fr" to "Manuel d'utilisation", "de" to "Bedienungsanleitung", "pt" to "Manual do usuário", "it" to "Manuale utente", "ja" to "取扱説明書", "ko" to "사용 설명서", "nl" to "Handleiding", "zh" to "用户手册"),
|
||||||
|
"doc.type.receipt" to mapOf("en" to "Receipt/Invoice", "es" to "Recibo/Factura", "fr" to "Reçu/Facture", "de" to "Beleg/Rechnung", "pt" to "Recibo/Nota fiscal", "it" to "Ricevuta/Fattura", "ja" to "領収書/請求書", "ko" to "영수증/청구서", "nl" to "Bon/factuur", "zh" to "收据/发票"),
|
||||||
|
"doc.type.inspection" to mapOf("en" to "Inspection Report", "es" to "Informe de inspección", "fr" to "Rapport d'inspection", "de" to "Prüfbericht", "pt" to "Laudo de vistoria", "it" to "Rapporto di ispezione", "ja" to "点検報告書", "ko" to "점검 보고서", "nl" to "Inspectierapport", "zh" to "检查报告"),
|
||||||
|
"doc.type.permit" to mapOf("en" to "Permit", "es" to "Permiso", "fr" to "Permis", "de" to "Genehmigung", "pt" to "Alvará", "it" to "Permesso", "ja" to "許可証", "ko" to "허가증", "nl" to "Vergunning", "zh" to "许可证"),
|
||||||
|
"doc.type.deed" to mapOf("en" to "Deed/Title", "es" to "Escritura/Título", "fr" to "Acte/Titre", "de" to "Urkunde/Eigentumstitel", "pt" to "Escritura/Título", "it" to "Atto/Titolo", "ja" to "権利証/登記", "ko" to "등기/권리증", "nl" to "Akte/eigendomsbewijs", "zh" to "房契/产权证"),
|
||||||
|
"doc.type.insurance" to mapOf("en" to "Insurance", "es" to "Seguro", "fr" to "Assurance", "de" to "Versicherung", "pt" to "Seguro", "it" to "Assicurazione", "ja" to "保険", "ko" to "보험", "nl" to "Verzekering", "zh" to "保险"),
|
||||||
|
"doc.type.contract" to mapOf("en" to "Contract", "es" to "Contrato", "fr" to "Contrat", "de" to "Vertrag", "pt" to "Contrato", "it" to "Contratto", "ja" to "契約書", "ko" to "계약서", "nl" to "Contract", "zh" to "合同"),
|
||||||
|
"doc.type.photo" to mapOf("en" to "Photo", "es" to "Foto", "fr" to "Photo", "de" to "Foto", "pt" to "Foto", "it" to "Foto", "ja" to "写真", "ko" to "사진", "nl" to "Foto", "zh" to "照片"),
|
||||||
|
"doc.type.other" to mapOf("en" to "Other", "es" to "Otro", "fr" to "Autre", "de" to "Sonstiges", "pt" to "Outro", "it" to "Altro", "ja" to "その他", "ko" to "기타", "nl" to "Overig", "zh" to "其他"),
|
||||||
|
"doc.category.appliance" to mapOf("en" to "Appliance", "es" to "Electrodoméstico", "fr" to "Appareil électroménager", "de" to "Gerät", "pt" to "Eletrodoméstico", "it" to "Elettrodomestico", "ja" to "家電", "ko" to "가전제품", "nl" to "Apparaat", "zh" to "家电"),
|
||||||
|
"doc.category.hvac" to mapOf("en" to "HVAC", "es" to "Climatización", "fr" to "CVC", "de" to "Heizung/Klima", "pt" to "Climatização", "it" to "HVAC", "ja" to "空調", "ko" to "냉난방", "nl" to "HVAC", "zh" to "暖通空调"),
|
||||||
|
"doc.category.plumbing" to mapOf("en" to "Plumbing", "es" to "Fontanería", "fr" to "Plomberie", "de" to "Sanitär", "pt" to "Hidráulica", "it" to "Idraulica", "ja" to "配管", "ko" to "배관", "nl" to "Loodgieterswerk", "zh" to "管道"),
|
||||||
|
"doc.category.electrical" to mapOf("en" to "Electrical", "es" to "Electricidad", "fr" to "Électricité", "de" to "Elektrik", "pt" to "Elétrica", "it" to "Impianto elettrico", "ja" to "電気", "ko" to "전기", "nl" to "Elektra", "zh" to "电气"),
|
||||||
|
"doc.category.roofing" to mapOf("en" to "Roofing", "es" to "Techado", "fr" to "Toiture", "de" to "Dach", "pt" to "Telhado", "it" to "Copertura", "ja" to "屋根", "ko" to "지붕", "nl" to "Dakbedekking", "zh" to "屋顶"),
|
||||||
|
"doc.category.structural" to mapOf("en" to "Structural", "es" to "Estructural", "fr" to "Structure", "de" to "Bausubstanz", "pt" to "Estrutural", "it" to "Struttura", "ja" to "構造", "ko" to "구조", "nl" to "Constructie", "zh" to "结构"),
|
||||||
|
"doc.category.landscaping" to mapOf("en" to "Landscaping", "es" to "Jardinería", "fr" to "Aménagement paysager", "de" to "Garten", "pt" to "Paisagismo", "it" to "Giardinaggio", "ja" to "造園", "ko" to "조경", "nl" to "Tuinaanleg", "zh" to "园艺绿化"),
|
||||||
|
"doc.category.general" to mapOf("en" to "General", "es" to "General", "fr" to "Général", "de" to "Allgemein", "pt" to "Geral", "it" to "Generale", "ja" to "一般", "ko" to "일반", "nl" to "Algemeen", "zh" to "通用"),
|
||||||
|
"doc.category.other" to mapOf("en" to "Other", "es" to "Otro", "fr" to "Autre", "de" to "Sonstiges", "pt" to "Outro", "it" to "Altro", "ja" to "その他", "ko" to "기타", "nl" to "Overig", "zh" to "其他"),
|
||||||
|
"validation.title_required" to mapOf("en" to "Title is required", "es" to "El título es obligatorio", "fr" to "Le titre est obligatoire", "de" to "Titel ist erforderlich", "pt" to "O título é obrigatório", "it" to "Il titolo è obbligatorio", "ja" to "タイトルは必須です", "ko" to "제목은 필수입니다", "nl" to "Titel is verplicht", "zh" to "标题为必填项"),
|
||||||
|
"validation.priority_required" to mapOf("en" to "Please select a priority", "es" to "Selecciona una prioridad", "fr" to "Veuillez sélectionner une priorité", "de" to "Bitte wähle eine Priorität aus", "pt" to "Selecione uma prioridade", "it" to "Seleziona una priorità", "ja" to "優先度を選択してください", "ko" to "우선순위를 선택하세요", "nl" to "Selecteer een prioriteit", "zh" to "请选择优先级"),
|
||||||
|
"validation.category_required" to mapOf("en" to "Please select a category", "es" to "Selecciona una categoría", "fr" to "Veuillez sélectionner une catégorie", "de" to "Bitte wähle eine Kategorie aus", "pt" to "Selecione uma categoria", "it" to "Seleziona una categoria", "ja" to "カテゴリを選択してください", "ko" to "카테고리를 선택하세요", "nl" to "Selecteer een categorie", "zh" to "请选择类别"),
|
||||||
|
"validation.frequency_required" to mapOf("en" to "Please select a frequency", "es" to "Selecciona una frecuencia", "fr" to "Veuillez sélectionner une fréquence", "de" to "Bitte wähle eine Häufigkeit aus", "pt" to "Selecione uma frequência", "it" to "Seleziona una frequenza", "ja" to "頻度を選択してください", "ko" to "빈도를 선택하세요", "nl" to "Selecteer een frequentie", "zh" to "请选择频率"),
|
||||||
|
"validation.est_cost_invalid" to mapOf("en" to "Estimated cost must be a valid number", "es" to "El costo estimado debe ser un número válido", "fr" to "Le coût estimé doit être un nombre valide", "de" to "Die geschätzten Kosten müssen eine gültige Zahl sein", "pt" to "O custo estimado deve ser um número válido", "it" to "Il costo stimato deve essere un numero valido", "ja" to "見積もり費用は有効な数値である必要があります", "ko" to "예상 비용은 유효한 숫자여야 합니다", "nl" to "Geschatte kosten moeten een geldig getal zijn", "zh" to "预估费用必须是有效的数字"),
|
||||||
|
"validation.property_required" to mapOf("en" to "Property is required", "es" to "La propiedad es obligatoria", "fr" to "La propriété est obligatoire", "de" to "Immobilie ist erforderlich", "pt" to "A propriedade é obrigatória", "it" to "La proprietà è obbligatoria", "ja" to "物件は必須です", "ko" to "건물은 필수입니다", "nl" to "Woning is verplicht", "zh" to "房产为必填项"),
|
||||||
|
"validation.name_required" to mapOf("en" to "Name is required", "es" to "El nombre es obligatorio", "fr" to "Le nom est obligatoire", "de" to "Name ist erforderlich", "pt" to "O nome é obrigatório", "it" to "Il nome è obbligatorio", "ja" to "名前は必須です", "ko" to "이름은 필수입니다", "nl" to "Naam is verplicht", "zh" to "名称为必填项"),
|
||||||
|
"validation.name_too_long" to mapOf("en" to "Name must be {0} characters or fewer", "es" to "El nombre debe tener {0} caracteres o menos", "fr" to "Le nom doit comporter {0} caractères ou moins", "de" to "Der Name darf höchstens {0} Zeichen lang sein", "pt" to "O nome deve ter {0} caracteres ou menos", "it" to "Il nome deve contenere {0} caratteri o meno", "ja" to "名前は{0}文字以内で入力してください", "ko" to "이름은 {0}자 이하여야 합니다", "nl" to "De naam mag maximaal {0} tekens bevatten", "zh" to "名称不得超过{0}个字符"),
|
||||||
|
"validation.bedrooms_invalid" to mapOf("en" to "Bedrooms must be a non-negative whole number", "es" to "Los dormitorios deben ser un número entero no negativo", "fr" to "Le nombre de chambres doit être un entier non négatif", "de" to "Schlafzimmer müssen eine nicht negative ganze Zahl sein", "pt" to "Os quartos devem ser um número inteiro não negativo", "it" to "Le camere da letto devono essere un numero intero non negativo", "ja" to "寝室数は0以上の整数である必要があります", "ko" to "침실 수는 0 이상의 정수여야 합니다", "nl" to "Slaapkamers moeten een niet-negatief geheel getal zijn", "zh" to "卧室数必须是非负整数"),
|
||||||
|
"validation.bathrooms_invalid" to mapOf("en" to "Bathrooms must be a non-negative number", "es" to "Los baños deben ser un número no negativo", "fr" to "Le nombre de salles de bain doit être un nombre non négatif", "de" to "Badezimmer müssen eine nicht negative Zahl sein", "pt" to "Os banheiros devem ser um número não negativo", "it" to "I bagni devono essere un numero non negativo", "ja" to "浴室数は0以上の数値である必要があります", "ko" to "욕실 수는 0 이상의 숫자여야 합니다", "nl" to "Badkamers moeten een niet-negatief getal zijn", "zh" to "浴室数必须是非负数"),
|
||||||
|
"validation.sqft_invalid" to mapOf("en" to "Square footage must be a positive whole number", "es" to "Los pies cuadrados deben ser un número entero positivo", "fr" to "La surface doit être un entier positif", "de" to "Die Quadratmeterzahl muss eine positive ganze Zahl sein", "pt" to "A metragem quadrada deve ser um número inteiro positivo", "it" to "La metratura deve essere un numero intero positivo", "ja" to "面積は正の整数である必要があります", "ko" to "면적은 양의 정수여야 합니다", "nl" to "De oppervlakte moet een positief geheel getal zijn", "zh" to "面积必须是正整数"),
|
||||||
|
"validation.lot_size_invalid" to mapOf("en" to "Lot size must be a positive number", "es" to "El tamaño del terreno debe ser un número positivo", "fr" to "La taille du terrain doit être un nombre positif", "de" to "Die Grundstücksgröße muss eine positive Zahl sein", "pt" to "O tamanho do lote deve ser um número positivo", "it" to "La dimensione del lotto deve essere un numero positivo", "ja" to "敷地面積は正の数値である必要があります", "ko" to "대지 면적은 양수여야 합니다", "nl" to "De perceelgrootte moet een positief getal zijn", "zh" to "占地面积必须是正数"),
|
||||||
|
"validation.year_built_format" to mapOf("en" to "Year built must be a 4-digit year", "es" to "El año de construcción debe tener 4 dígitos", "fr" to "L'année de construction doit comporter 4 chiffres", "de" to "Das Baujahr muss eine 4-stellige Jahreszahl sein", "pt" to "O ano de construção deve ter 4 dígitos", "it" to "L'anno di costruzione deve essere di 4 cifre", "ja" to "築年は4桁の西暦で入力してください", "ko" to "건축 연도는 4자리 연도여야 합니다", "nl" to "Het bouwjaar moet een jaartal van 4 cijfers zijn", "zh" to "建造年份必须为4位数年份"),
|
||||||
|
"validation.year_built_range" to mapOf("en" to "Year built must be between {0} and the current year", "es" to "El año de construcción debe estar entre {0} y el año actual", "fr" to "L'année de construction doit être comprise entre {0} et l'année actuelle", "de" to "Das Baujahr muss zwischen {0} und dem aktuellen Jahr liegen", "pt" to "O ano de construção deve estar entre {0} e o ano atual", "it" to "L'anno di costruzione deve essere compreso tra {0} e l'anno corrente", "ja" to "築年は{0}年から現在の年までの間で入力してください", "ko" to "건축 연도는 {0}년부터 현재 연도 사이여야 합니다", "nl" to "Het bouwjaar moet tussen {0} en het huidige jaar liggen", "zh" to "建造年份必须介于{0}和当前年份之间"),
|
||||||
|
"validation.share_code_length" to mapOf("en" to "Share code must be 6 characters", "es" to "El código para compartir debe tener 6 caracteres", "fr" to "Le code de partage doit comporter 6 caractères", "de" to "Der Freigabecode muss 6 Zeichen lang sein", "pt" to "O código de compartilhamento deve ter 6 caracteres", "it" to "Il codice di condivisione deve avere 6 caratteri", "ja" to "共有コードは6文字である必要があります", "ko" to "공유 코드는 6자여야 합니다", "nl" to "De deelcode moet 6 tekens lang zijn", "zh" to "分享码必须为6个字符"),
|
||||||
|
"auth.recovery_sent" to mapOf("en" to "If that email exists, a recovery code has been sent.", "es" to "Si ese correo existe, se ha enviado un código de recuperación.", "fr" to "Si cet e-mail existe, un code de récupération a été envoyé.", "de" to "Falls diese E-Mail existiert, wurde ein Wiederherstellungscode gesendet.", "pt" to "Se esse e-mail existir, um código de recuperação foi enviado.", "it" to "Se questa email esiste, è stato inviato un codice di recupero.", "ja" to "そのメールアドレスが登録されている場合、復旧コードを送信しました。", "ko" to "해당 이메일이 존재하는 경우 복구 코드가 전송되었습니다.", "nl" to "Als dat e-mailadres bestaat, is er een herstelcode verzonden.", "zh" to "如果该邮箱存在,恢复代码已发送。"),
|
||||||
|
"auth.code_verified" to mapOf("en" to "Code verified.", "es" to "Código verificado.", "fr" to "Code vérifié.", "de" to "Code verifiziert.", "pt" to "Código verificado.", "it" to "Codice verificato.", "ja" to "コードを確認しました。", "ko" to "코드가 확인되었습니다.", "nl" to "Code geverifieerd.", "zh" to "代码已验证。"),
|
||||||
|
"auth.password_updated" to mapOf("en" to "Password updated. You can now sign in.", "es" to "Contraseña actualizada. Ya puedes iniciar sesión.", "fr" to "Mot de passe mis à jour. Vous pouvez maintenant vous connecter.", "de" to "Passwort aktualisiert. Du kannst dich jetzt anmelden.", "pt" to "Senha atualizada. Agora você pode entrar.", "it" to "Password aggiornata. Ora puoi accedere.", "ja" to "パスワードを更新しました。サインインできます。", "ko" to "비밀번호가 업데이트되었습니다. 이제 로그인할 수 있습니다.", "nl" to "Wachtwoord bijgewerkt. Je kunt nu inloggen.", "zh" to "密码已更新。您现在可以登录。"),
|
||||||
|
"auth.email_verified" to mapOf("en" to "Email verified.", "es" to "Correo verificado.", "fr" to "E-mail vérifié.", "de" to "E-Mail verifiziert.", "pt" to "E-mail verificado.", "it" to "Email verificata.", "ja" to "メールアドレスを確認しました。", "ko" to "이메일이 확인되었습니다.", "nl" to "E-mail geverifieerd.", "zh" to "邮箱已验证。"),
|
||||||
|
// Home profile picker fallback option labels (apiValue stays stable; these localize display)
|
||||||
|
"home_profile.heating.gas_furnace" to mapOf("en" to "Gas Furnace", "es" to "Caldera de gas", "fr" to "Chaudière à gaz", "de" to "Gasheizung", "pt" to "Aquecedor a gás", "it" to "Caldaia a gas", "ja" to "ガス暖房", "ko" to "가스 난방", "nl" to "Gasverwarming", "zh" to "燃气炉"),
|
||||||
|
"home_profile.heating.electric" to mapOf("en" to "Electric", "es" to "Eléctrica", "fr" to "Électrique", "de" to "Elektrisch", "pt" to "Elétrico", "it" to "Elettrico", "ja" to "電気", "ko" to "전기", "nl" to "Elektrisch", "zh" to "电力"),
|
||||||
|
"home_profile.heating.heat_pump" to mapOf("en" to "Heat Pump", "es" to "Bomba de calor", "fr" to "Pompe à chaleur", "de" to "Wärmepumpe", "pt" to "Bomba de calor", "it" to "Pompa di calore", "ja" to "ヒートポンプ", "ko" to "히트 펌프", "nl" to "Warmtepomp", "zh" to "热泵"),
|
||||||
|
"home_profile.heating.boiler" to mapOf("en" to "Boiler", "es" to "Caldera", "fr" to "Chaudière", "de" to "Heizkessel", "pt" to "Caldeira", "it" to "Caldaia", "ja" to "ボイラー", "ko" to "보일러", "nl" to "Ketel", "zh" to "锅炉"),
|
||||||
|
"home_profile.heating.radiant" to mapOf("en" to "Radiant", "es" to "Radiante", "fr" to "Rayonnant", "de" to "Strahlungsheizung", "pt" to "Radiante", "it" to "Radiante", "ja" to "輻射熱", "ko" to "복사 난방", "nl" to "Stralingsverwarming", "zh" to "辐射供暖"),
|
||||||
|
"home_profile.heating.wood_stove" to mapOf("en" to "Wood Stove", "es" to "Estufa de leña", "fr" to "Poêle à bois", "de" to "Holzofen", "pt" to "Fogão a lenha", "it" to "Stufa a legna", "ja" to "薪ストーブ", "ko" to "장작 난로", "nl" to "Houtkachel", "zh" to "柴火炉"),
|
||||||
|
"home_profile.heating.none" to mapOf("en" to "None", "es" to "Ninguna", "fr" to "Aucun", "de" to "Keine", "pt" to "Nenhum", "it" to "Nessuno", "ja" to "なし", "ko" to "없음", "nl" to "Geen", "zh" to "无"),
|
||||||
|
"home_profile.cooling.central_ac" to mapOf("en" to "Central AC", "es" to "Aire central", "fr" to "Clim centrale", "de" to "Zentrale Klimaanlage", "pt" to "Ar central", "it" to "Climatizzazione centrale", "ja" to "セントラル空調", "ko" to "중앙 냉방", "nl" to "Centrale airco", "zh" to "中央空调"),
|
||||||
|
"home_profile.cooling.window_unit" to mapOf("en" to "Window Unit", "es" to "Equipo de ventana", "fr" to "Climatiseur de fenêtre", "de" to "Fenstergerät", "pt" to "Aparelho de janela", "it" to "Condizionatore da finestra", "ja" to "窓用エアコン", "ko" to "창문형 에어컨", "nl" to "Raamunit", "zh" to "窗式空调"),
|
||||||
|
"home_profile.cooling.mini_split" to mapOf("en" to "Mini Split", "es" to "Mini split", "fr" to "Mini-split", "de" to "Mini-Split", "pt" to "Mini split", "it" to "Mini split", "ja" to "ミニスプリット", "ko" to "미니 스플릿", "nl" to "Mini-split", "zh" to "分体式空调"),
|
||||||
|
"home_profile.cooling.evaporative" to mapOf("en" to "Evaporative", "es" to "Evaporativo", "fr" to "Évaporatif", "de" to "Verdunstung", "pt" to "Evaporativo", "it" to "Evaporativo", "ja" to "気化式", "ko" to "증발식", "nl" to "Verdampingskoeling", "zh" to "蒸发式"),
|
||||||
|
"home_profile.cooling.none" to mapOf("en" to "None", "es" to "Ninguno", "fr" to "Aucun", "de" to "Keine", "pt" to "Nenhum", "it" to "Nessuno", "ja" to "なし", "ko" to "없음", "nl" to "Geen", "zh" to "无"),
|
||||||
|
"home_profile.water_heater.tank_gas" to mapOf("en" to "Tank (Gas)", "es" to "Tanque (gas)", "fr" to "Réservoir (gaz)", "de" to "Speicher (Gas)", "pt" to "Boiler (gás)", "it" to "Serbatoio (gas)", "ja" to "タンク(ガス)", "ko" to "탱크 (가스)", "nl" to "Boiler (gas)", "zh" to "储水式(燃气)"),
|
||||||
|
"home_profile.water_heater.tank_electric" to mapOf("en" to "Tank (Electric)", "es" to "Tanque (eléctrico)", "fr" to "Réservoir (électrique)", "de" to "Speicher (elektrisch)", "pt" to "Boiler (elétrico)", "it" to "Serbatoio (elettrico)", "ja" to "タンク(電気)", "ko" to "탱크 (전기)", "nl" to "Boiler (elektrisch)", "zh" to "储水式(电)"),
|
||||||
|
"home_profile.water_heater.tankless" to mapOf("en" to "Tankless", "es" to "Sin tanque", "fr" to "Sans réservoir", "de" to "Durchlauferhitzer", "pt" to "Sem tanque", "it" to "Istantaneo", "ja" to "タンクレス", "ko" to "순간식", "nl" to "Doorstroom", "zh" to "即热式"),
|
||||||
|
"home_profile.water_heater.solar" to mapOf("en" to "Solar", "es" to "Solar", "fr" to "Solaire", "de" to "Solar", "pt" to "Solar", "it" to "Solare", "ja" to "ソーラー", "ko" to "태양열", "nl" to "Zonne-energie", "zh" to "太阳能"),
|
||||||
|
"home_profile.water_heater.heat_pump" to mapOf("en" to "Heat Pump", "es" to "Bomba de calor", "fr" to "Pompe à chaleur", "de" to "Wärmepumpe", "pt" to "Bomba de calor", "it" to "Pompa di calore", "ja" to "ヒートポンプ", "ko" to "히트 펌프", "nl" to "Warmtepomp", "zh" to "热泵"),
|
||||||
|
"home_profile.roof.asphalt_shingle" to mapOf("en" to "Asphalt Shingle", "es" to "Teja asfáltica", "fr" to "Bardeau d'asphalte", "de" to "Asphaltschindel", "pt" to "Telha asfáltica", "it" to "Tegola bituminosa", "ja" to "アスファルトシングル", "ko" to "아스팔트 슁글", "nl" to "Asfaltshingle", "zh" to "沥青瓦"),
|
||||||
|
"home_profile.roof.metal" to mapOf("en" to "Metal", "es" to "Metal", "fr" to "Métal", "de" to "Metall", "pt" to "Metal", "it" to "Metallo", "ja" to "金属", "ko" to "금속", "nl" to "Metaal", "zh" to "金属"),
|
||||||
|
"home_profile.roof.tile" to mapOf("en" to "Tile", "es" to "Teja", "fr" to "Tuile", "de" to "Ziegel", "pt" to "Telha", "it" to "Tegola", "ja" to "瓦", "ko" to "기와", "nl" to "Dakpan", "zh" to "瓦片"),
|
||||||
|
"home_profile.roof.flat_tpo" to mapOf("en" to "Flat/TPO", "es" to "Plano/TPO", "fr" to "Plat/TPO", "de" to "Flach/TPO", "pt" to "Plano/TPO", "it" to "Piano/TPO", "ja" to "陸屋根/TPO", "ko" to "평지붕/TPO", "nl" to "Plat/TPO", "zh" to "平顶/TPO"),
|
||||||
|
"home_profile.roof.slate" to mapOf("en" to "Slate", "es" to "Pizarra", "fr" to "Ardoise", "de" to "Schiefer", "pt" to "Ardósia", "it" to "Ardesia", "ja" to "スレート", "ko" to "슬레이트", "nl" to "Leisteen", "zh" to "石板"),
|
||||||
|
"home_profile.roof.wood_shake" to mapOf("en" to "Wood Shake", "es" to "Tejamanil de madera", "fr" to "Bardeau de bois", "de" to "Holzschindel", "pt" to "Telha de madeira", "it" to "Scandola di legno", "ja" to "木製シェイク", "ko" to "목재 셰이크", "nl" to "Houten shingle", "zh" to "木瓦"),
|
||||||
|
"home_profile.exterior.vinyl_siding" to mapOf("en" to "Vinyl Siding", "es" to "Revestimiento de vinilo", "fr" to "Revêtement en vinyle", "de" to "Vinylverkleidung", "pt" to "Revestimento de vinil", "it" to "Rivestimento in vinile", "ja" to "ビニールサイディング", "ko" to "비닐 사이딩", "nl" to "Vinyl gevelbekleding", "zh" to "乙烯基壁板"),
|
||||||
|
"home_profile.exterior.brick" to mapOf("en" to "Brick", "es" to "Ladrillo", "fr" to "Brique", "de" to "Ziegelstein", "pt" to "Tijolo", "it" to "Mattone", "ja" to "レンガ", "ko" to "벽돌", "nl" to "Baksteen", "zh" to "砖"),
|
||||||
|
"home_profile.exterior.stucco" to mapOf("en" to "Stucco", "es" to "Estuco", "fr" to "Stuc", "de" to "Putz", "pt" to "Estuque", "it" to "Stucco", "ja" to "スタッコ", "ko" to "스투코", "nl" to "Stucwerk", "zh" to "灰泥"),
|
||||||
|
"home_profile.exterior.wood" to mapOf("en" to "Wood", "es" to "Madera", "fr" to "Bois", "de" to "Holz", "pt" to "Madeira", "it" to "Legno", "ja" to "木材", "ko" to "목재", "nl" to "Hout", "zh" to "木材"),
|
||||||
|
"home_profile.exterior.stone" to mapOf("en" to "Stone", "es" to "Piedra", "fr" to "Pierre", "de" to "Stein", "pt" to "Pedra", "it" to "Pietra", "ja" to "石", "ko" to "석재", "nl" to "Steen", "zh" to "石材"),
|
||||||
|
"home_profile.exterior.fiber_cement" to mapOf("en" to "Fiber Cement", "es" to "Fibrocemento", "fr" to "Fibrociment", "de" to "Faserzement", "pt" to "Fibrocimento", "it" to "Fibrocemento", "ja" to "繊維セメント", "ko" to "섬유 시멘트", "nl" to "Vezelcement", "zh" to "纤维水泥"),
|
||||||
|
"home_profile.flooring.hardwood" to mapOf("en" to "Hardwood", "es" to "Madera maciza", "fr" to "Bois massif", "de" to "Hartholz", "pt" to "Madeira maciça", "it" to "Legno massello", "ja" to "無垢材", "ko" to "원목", "nl" to "Hardhout", "zh" to "实木"),
|
||||||
|
"home_profile.flooring.carpet" to mapOf("en" to "Carpet", "es" to "Alfombra", "fr" to "Moquette", "de" to "Teppich", "pt" to "Carpete", "it" to "Moquette", "ja" to "カーペット", "ko" to "카펫", "nl" to "Tapijt", "zh" to "地毯"),
|
||||||
|
"home_profile.flooring.tile" to mapOf("en" to "Tile", "es" to "Baldosa", "fr" to "Carrelage", "de" to "Fliesen", "pt" to "Cerâmica", "it" to "Piastrelle", "ja" to "タイル", "ko" to "타일", "nl" to "Tegel", "zh" to "瓷砖"),
|
||||||
|
"home_profile.flooring.laminate" to mapOf("en" to "Laminate", "es" to "Laminado", "fr" to "Stratifié", "de" to "Laminat", "pt" to "Laminado", "it" to "Laminato", "ja" to "ラミネート", "ko" to "라미네이트", "nl" to "Laminaat", "zh" to "复合地板"),
|
||||||
|
"home_profile.flooring.vinyl" to mapOf("en" to "Vinyl", "es" to "Vinilo", "fr" to "Vinyle", "de" to "Vinyl", "pt" to "Vinil", "it" to "Vinile", "ja" to "ビニール", "ko" to "비닐", "nl" to "Vinyl", "zh" to "乙烯基"),
|
||||||
|
"home_profile.landscaping.lawn" to mapOf("en" to "Lawn", "es" to "Césped", "fr" to "Pelouse", "de" to "Rasen", "pt" to "Gramado", "it" to "Prato", "ja" to "芝生", "ko" to "잔디", "nl" to "Gazon", "zh" to "草坪"),
|
||||||
|
"home_profile.landscaping.xeriscaping" to mapOf("en" to "Xeriscaping", "es" to "Xerojardinería", "fr" to "Xéropaysagisme", "de" to "Xeriscaping", "pt" to "Xerojardinagem", "it" to "Xeriscaping", "ja" to "ゼリスケープ", "ko" to "건조 조경", "nl" to "Xeriscaping", "zh" to "旱生园艺"),
|
||||||
|
"home_profile.landscaping.none" to mapOf("en" to "None", "es" to "Ninguno", "fr" to "Aucun", "de" to "Keine", "pt" to "Nenhum", "it" to "Nessuno", "ja" to "なし", "ko" to "없음", "nl" to "Geen", "zh" to "无"),
|
||||||
|
)
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.tt.honeyDue.models
|
package com.tt.honeyDue.models
|
||||||
|
|
||||||
|
import com.tt.honeyDue.i18n.ClientStrings
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@@ -120,17 +121,19 @@ data class DocumentUpdateRequest(
|
|||||||
// API now returns List<Document> directly
|
// API now returns List<Document> directly
|
||||||
|
|
||||||
// Document type choices
|
// Document type choices
|
||||||
enum class DocumentType(val value: String, val displayName: String) {
|
enum class DocumentType(val value: String, private val displayNameKey: String) {
|
||||||
WARRANTY("warranty", "Warranty"),
|
WARRANTY("warranty", "doc.type.warranty"),
|
||||||
MANUAL("manual", "User Manual"),
|
MANUAL("manual", "doc.type.manual"),
|
||||||
RECEIPT("receipt", "Receipt/Invoice"),
|
RECEIPT("receipt", "doc.type.receipt"),
|
||||||
INSPECTION("inspection", "Inspection Report"),
|
INSPECTION("inspection", "doc.type.inspection"),
|
||||||
PERMIT("permit", "Permit"),
|
PERMIT("permit", "doc.type.permit"),
|
||||||
DEED("deed", "Deed/Title"),
|
DEED("deed", "doc.type.deed"),
|
||||||
INSURANCE("insurance", "Insurance"),
|
INSURANCE("insurance", "doc.type.insurance"),
|
||||||
CONTRACT("contract", "Contract"),
|
CONTRACT("contract", "doc.type.contract"),
|
||||||
PHOTO("photo", "Photo"),
|
PHOTO("photo", "doc.type.photo"),
|
||||||
OTHER("other", "Other");
|
OTHER("other", "doc.type.other");
|
||||||
|
|
||||||
|
val displayName: String get() = ClientStrings.t(displayNameKey)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromValue(value: String): DocumentType {
|
fun fromValue(value: String): DocumentType {
|
||||||
@@ -140,16 +143,18 @@ enum class DocumentType(val value: String, val displayName: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Document/Warranty category choices
|
// Document/Warranty category choices
|
||||||
enum class DocumentCategory(val value: String, val displayName: String) {
|
enum class DocumentCategory(val value: String, private val displayNameKey: String) {
|
||||||
APPLIANCE("appliance", "Appliance"),
|
APPLIANCE("appliance", "doc.category.appliance"),
|
||||||
HVAC("hvac", "HVAC"),
|
HVAC("hvac", "doc.category.hvac"),
|
||||||
PLUMBING("plumbing", "Plumbing"),
|
PLUMBING("plumbing", "doc.category.plumbing"),
|
||||||
ELECTRICAL("electrical", "Electrical"),
|
ELECTRICAL("electrical", "doc.category.electrical"),
|
||||||
ROOFING("roofing", "Roofing"),
|
ROOFING("roofing", "doc.category.roofing"),
|
||||||
STRUCTURAL("structural", "Structural"),
|
STRUCTURAL("structural", "doc.category.structural"),
|
||||||
LANDSCAPING("landscaping", "Landscaping"),
|
LANDSCAPING("landscaping", "doc.category.landscaping"),
|
||||||
GENERAL("general", "General"),
|
GENERAL("general", "doc.category.general"),
|
||||||
OTHER("other", "Other");
|
OTHER("other", "doc.category.other");
|
||||||
|
|
||||||
|
val displayName: String get() = ClientStrings.t(displayNameKey)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromValue(value: String): DocumentCategory {
|
fun fromValue(value: String): DocumentCategory {
|
||||||
|
|||||||
@@ -1,59 +1,62 @@
|
|||||||
package com.tt.honeyDue.models
|
package com.tt.honeyDue.models
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static option lists for home profile pickers.
|
* Static option lists for home profile pickers (live fallback shown when the
|
||||||
* Each entry is a (apiValue, displayLabel) pair.
|
* backend options aren't loaded). Each entry is a (apiValue, displayLabelKey)
|
||||||
|
* pair: the FIRST element is the stable backend API value and must never
|
||||||
|
* change; the SECOND element is a [com.tt.honeyDue.i18n.ClientStrings] key
|
||||||
|
* resolved to a localized label at the render site.
|
||||||
*/
|
*/
|
||||||
object HomeProfileOptions {
|
object HomeProfileOptions {
|
||||||
val heatingTypes = listOf(
|
val heatingTypes = listOf(
|
||||||
"gas_furnace" to "Gas Furnace",
|
"gas_furnace" to "home_profile.heating.gas_furnace",
|
||||||
"electric" to "Electric",
|
"electric" to "home_profile.heating.electric",
|
||||||
"heat_pump" to "Heat Pump",
|
"heat_pump" to "home_profile.heating.heat_pump",
|
||||||
"boiler" to "Boiler",
|
"boiler" to "home_profile.heating.boiler",
|
||||||
"radiant" to "Radiant",
|
"radiant" to "home_profile.heating.radiant",
|
||||||
"wood_stove" to "Wood Stove",
|
"wood_stove" to "home_profile.heating.wood_stove",
|
||||||
"none" to "None"
|
"none" to "home_profile.heating.none"
|
||||||
)
|
)
|
||||||
val coolingTypes = listOf(
|
val coolingTypes = listOf(
|
||||||
"central_ac" to "Central AC",
|
"central_ac" to "home_profile.cooling.central_ac",
|
||||||
"window_unit" to "Window Unit",
|
"window_unit" to "home_profile.cooling.window_unit",
|
||||||
"mini_split" to "Mini Split",
|
"mini_split" to "home_profile.cooling.mini_split",
|
||||||
"evaporative" to "Evaporative",
|
"evaporative" to "home_profile.cooling.evaporative",
|
||||||
"none" to "None"
|
"none" to "home_profile.cooling.none"
|
||||||
)
|
)
|
||||||
val waterHeaterTypes = listOf(
|
val waterHeaterTypes = listOf(
|
||||||
"tank_gas" to "Tank (Gas)",
|
"tank_gas" to "home_profile.water_heater.tank_gas",
|
||||||
"tank_electric" to "Tank (Electric)",
|
"tank_electric" to "home_profile.water_heater.tank_electric",
|
||||||
"tankless" to "Tankless",
|
"tankless" to "home_profile.water_heater.tankless",
|
||||||
"solar" to "Solar",
|
"solar" to "home_profile.water_heater.solar",
|
||||||
"heat_pump_wh" to "Heat Pump"
|
"heat_pump_wh" to "home_profile.water_heater.heat_pump"
|
||||||
)
|
)
|
||||||
val roofTypes = listOf(
|
val roofTypes = listOf(
|
||||||
"asphalt_shingle" to "Asphalt Shingle",
|
"asphalt_shingle" to "home_profile.roof.asphalt_shingle",
|
||||||
"metal" to "Metal",
|
"metal" to "home_profile.roof.metal",
|
||||||
"tile" to "Tile",
|
"tile" to "home_profile.roof.tile",
|
||||||
"flat_tpo" to "Flat/TPO",
|
"flat_tpo" to "home_profile.roof.flat_tpo",
|
||||||
"slate" to "Slate",
|
"slate" to "home_profile.roof.slate",
|
||||||
"wood_shake" to "Wood Shake"
|
"wood_shake" to "home_profile.roof.wood_shake"
|
||||||
)
|
)
|
||||||
val exteriorTypes = listOf(
|
val exteriorTypes = listOf(
|
||||||
"vinyl_siding" to "Vinyl Siding",
|
"vinyl_siding" to "home_profile.exterior.vinyl_siding",
|
||||||
"brick" to "Brick",
|
"brick" to "home_profile.exterior.brick",
|
||||||
"stucco" to "Stucco",
|
"stucco" to "home_profile.exterior.stucco",
|
||||||
"wood" to "Wood",
|
"wood" to "home_profile.exterior.wood",
|
||||||
"stone" to "Stone",
|
"stone" to "home_profile.exterior.stone",
|
||||||
"fiber_cement" to "Fiber Cement"
|
"fiber_cement" to "home_profile.exterior.fiber_cement"
|
||||||
)
|
)
|
||||||
val flooringTypes = listOf(
|
val flooringTypes = listOf(
|
||||||
"hardwood" to "Hardwood",
|
"hardwood" to "home_profile.flooring.hardwood",
|
||||||
"carpet" to "Carpet",
|
"carpet" to "home_profile.flooring.carpet",
|
||||||
"tile" to "Tile",
|
"tile" to "home_profile.flooring.tile",
|
||||||
"laminate" to "Laminate",
|
"laminate" to "home_profile.flooring.laminate",
|
||||||
"vinyl" to "Vinyl"
|
"vinyl" to "home_profile.flooring.vinyl"
|
||||||
)
|
)
|
||||||
val landscapingTypes = listOf(
|
val landscapingTypes = listOf(
|
||||||
"lawn" to "Lawn",
|
"lawn" to "home_profile.landscaping.lawn",
|
||||||
"xeriscaping" to "Xeriscaping",
|
"xeriscaping" to "home_profile.landscaping.xeriscaping",
|
||||||
"none" to "None"
|
"none" to "home_profile.landscaping.none"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,12 +59,29 @@ object HoneyDueShareCodec {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a filesystem-safe package filename with `.honeydue` extension.
|
* Build a filesystem-safe package filename with `.honeydue` extension.
|
||||||
|
*
|
||||||
|
* Strips only the characters that are actually unsafe on iOS / Android
|
||||||
|
* filesystems (`/`, `\`, `:`, `*`, `?`, `"`, `<`, `>`, `|`, control
|
||||||
|
* chars). Spaces and apostrophes are kept intact so the recipient sees
|
||||||
|
* the original residence / contractor name in the iOS QuickLook title
|
||||||
|
* bar — gitea#7 called out the previous behaviour rendering
|
||||||
|
* "The_Tartt's" instead of "The Tartt's". Internal whitespace is
|
||||||
|
* collapsed to single spaces and trimmed; falls back to "honeyDue" if
|
||||||
|
* the input is blank after sanitising.
|
||||||
*/
|
*/
|
||||||
fun safeShareFileName(displayName: String): String {
|
fun safeShareFileName(displayName: String): String {
|
||||||
val safeName = displayName
|
val safeName = displayName
|
||||||
.replace(" ", "_")
|
// Keep whitespace through the filter so adjacent space+tab
|
||||||
.replace("/", "-")
|
// sequences survive to the regex-collapse step below. Drop
|
||||||
|
// only non-whitespace control chars (NUL etc.) plus the
|
||||||
|
// explicit filesystem-unsafe set.
|
||||||
|
.filter { it !in UNSAFE_FILENAME_CHARS && (it.isWhitespace() || !it.isISOControl()) }
|
||||||
|
.replace(Regex("\\s+"), " ")
|
||||||
|
.trim()
|
||||||
.take(50)
|
.take(50)
|
||||||
|
.ifBlank { "honeyDue" }
|
||||||
return "$safeName.honeydue"
|
return "$safeName.honeydue"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val UNSAFE_FILENAME_CHARS = setOf('/', '\\', ':', '*', '?', '"', '<', '>', '|')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,261 @@
|
|||||||
|
package com.tt.honeyDue.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Models for Ory Kratos native (`api`) self-service flows.
|
||||||
|
*
|
||||||
|
* honeyDue's Go API no longer owns identity — Ory Kratos does. The mobile
|
||||||
|
* client drives Kratos' native flows directly:
|
||||||
|
*
|
||||||
|
* 1. `GET {kratos}/self-service/{flow}/api` -> a flow object (id + ui.action)
|
||||||
|
* 2. `POST {ui.action}` -> success body or a re-rendered
|
||||||
|
* flow carrying validation messages in `ui.nodes[].messages` / `ui.messages`.
|
||||||
|
*
|
||||||
|
* Only the fields the client actually needs are modelled; `ignoreUnknownKeys`
|
||||||
|
* on the Json instance tolerates the rest of Kratos' (large) payloads.
|
||||||
|
*
|
||||||
|
* Kratos docs: https://www.ory.sh/docs/kratos/self-service
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ==================== Flow envelope ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Kratos self-service flow (login / registration / recovery / verification).
|
||||||
|
* Returned by the initial `GET .../{flow}/api` call.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class KratosFlow(
|
||||||
|
val id: String,
|
||||||
|
val type: String? = null,
|
||||||
|
@SerialName("expires_at") val expiresAt: String? = null,
|
||||||
|
@SerialName("issued_at") val issuedAt: String? = null,
|
||||||
|
val ui: KratosUi,
|
||||||
|
/** Present on a verification/recovery flow that is already complete. */
|
||||||
|
val state: String? = null,
|
||||||
|
/**
|
||||||
|
* Post-submission instructions. On a completed recovery flow this carries
|
||||||
|
* the privileged session token (`set_ory_session_token`) and the settings
|
||||||
|
* flow to finish the password change in (`show_settings_ui`).
|
||||||
|
*
|
||||||
|
* Nullable because Kratos serialises this as an explicit `"continue_with":
|
||||||
|
* null` (not an empty array / absent key) when there are no items — a
|
||||||
|
* non-nullable List would throw on decode ("Expected start of the array
|
||||||
|
* '[', but had 'n'").
|
||||||
|
*/
|
||||||
|
@SerialName("continue_with") val continueWith: List<KratosContinueWith>? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The renderable UI of a flow. `action` is the absolute URL the client must
|
||||||
|
* POST the method payload to; `messages` carries flow-level errors.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class KratosUi(
|
||||||
|
val action: String,
|
||||||
|
val method: String = "POST",
|
||||||
|
val nodes: List<KratosUiNode> = emptyList(),
|
||||||
|
val messages: List<KratosMessage> = emptyList(),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class KratosUiNode(
|
||||||
|
val type: String? = null,
|
||||||
|
val group: String? = null,
|
||||||
|
val attributes: KratosUiNodeAttributes? = null,
|
||||||
|
val messages: List<KratosMessage> = emptyList(),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class KratosUiNodeAttributes(
|
||||||
|
val name: String? = null,
|
||||||
|
val type: String? = null,
|
||||||
|
val value: JsonElement? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Kratos UI message. `type` is `info`, `error`, or `success`.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class KratosMessage(
|
||||||
|
val id: Long? = null,
|
||||||
|
val text: String,
|
||||||
|
val type: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
// ==================== Flow success bodies ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identity traits as configured in the Kratos identity schema for honeyDue.
|
||||||
|
* `email` is the primary identifier; `name` mirrors the schema's nested object.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class KratosTraits(
|
||||||
|
val email: String,
|
||||||
|
val name: KratosName? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class KratosName(
|
||||||
|
val first: String = "",
|
||||||
|
val last: String = "",
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Kratos identity (subset). Returned nested inside [KratosSession].
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class KratosIdentity(
|
||||||
|
val id: String,
|
||||||
|
@SerialName("schema_id") val schemaId: String? = null,
|
||||||
|
val state: String? = null,
|
||||||
|
val traits: KratosTraits? = null,
|
||||||
|
@SerialName("verifiable_addresses") val verifiableAddresses: List<KratosVerifiableAddress>? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class KratosVerifiableAddress(
|
||||||
|
val id: String? = null,
|
||||||
|
val value: String? = null,
|
||||||
|
val verified: Boolean = false,
|
||||||
|
val via: String? = null,
|
||||||
|
val status: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Kratos session. `active` + `expires_at` describe validity; `identity`
|
||||||
|
* carries the authenticated user's traits.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class KratosSession(
|
||||||
|
val id: String,
|
||||||
|
val active: Boolean = true,
|
||||||
|
@SerialName("expires_at") val expiresAt: String? = null,
|
||||||
|
@SerialName("authenticated_at") val authenticatedAt: String? = null,
|
||||||
|
val identity: KratosIdentity? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success body of a native login flow submission
|
||||||
|
* (`POST .../self-service/login/api`).
|
||||||
|
*
|
||||||
|
* `continue_with` is normally empty on a login response. The one case it
|
||||||
|
* isn't: Kratos v26 transparently registers a previously unseen OIDC
|
||||||
|
* identity when an `oidc` method is submitted to the login flow and no
|
||||||
|
* matching identity exists. In that scenario Kratos runs the registration
|
||||||
|
* flow internally and returns the result through the **login** endpoint —
|
||||||
|
* with `continue_with` carrying the `show_verification_ui` item that
|
||||||
|
* normal registration would have surfaced. We capture it here so the
|
||||||
|
* email-verification screen can submit the code to the correct flow.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class KratosLoginSuccess(
|
||||||
|
val session: KratosSession,
|
||||||
|
@SerialName("session_token") val sessionToken: String,
|
||||||
|
// Nullable: Kratos sends "continue_with": null on a plain login.
|
||||||
|
@SerialName("continue_with") val continueWith: List<KratosContinueWith>? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success body of a native registration flow submission
|
||||||
|
* (`POST .../self-service/registration/api`). With the
|
||||||
|
* `session` after-hook enabled, Kratos returns a session + token here.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class KratosRegistrationSuccess(
|
||||||
|
val session: KratosSession? = null,
|
||||||
|
@SerialName("session_token") val sessionToken: String? = null,
|
||||||
|
val identity: KratosIdentity? = null,
|
||||||
|
// Nullable: Kratos may serialise this as an explicit null.
|
||||||
|
@SerialName("continue_with") val continueWith: List<KratosContinueWith>? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A `continue_with` item. `action` is one of `show_verification_ui`,
|
||||||
|
* `show_settings_ui`, `set_ory_session_token`, etc. — see Kratos docs.
|
||||||
|
* `flow` is present for the `show_*_ui` actions; `orySessionToken` is present
|
||||||
|
* for `set_ory_session_token` (the privileged session a recovery flow issues).
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class KratosContinueWith(
|
||||||
|
val action: String? = null,
|
||||||
|
val flow: KratosContinueWithFlow? = null,
|
||||||
|
@SerialName("ory_session_token") val orySessionToken: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class KratosContinueWithFlow(
|
||||||
|
val id: String? = null,
|
||||||
|
@SerialName("verifiable_address") val verifiableAddress: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
// ==================== Submit payloads ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Body for submitting the `password` method to a login flow.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class KratosPasswordLoginBody(
|
||||||
|
val method: String = "password",
|
||||||
|
val identifier: String,
|
||||||
|
val password: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Body for submitting the `password` method to a registration flow.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class KratosPasswordRegistrationBody(
|
||||||
|
val method: String = "password",
|
||||||
|
val traits: KratosTraits,
|
||||||
|
val password: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Body for submitting an OIDC (`apple` / `google`) provider to a
|
||||||
|
* login or registration flow using a native `id_token` from the
|
||||||
|
* platform sign-in SDK.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class KratosOidcBody(
|
||||||
|
val method: String = "oidc",
|
||||||
|
val provider: String,
|
||||||
|
@SerialName("id_token") val idToken: String,
|
||||||
|
/** Optional traits — sent on registration so Kratos can seed the identity. */
|
||||||
|
val traits: KratosTraits? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Body for submitting the `code` method to a recovery flow.
|
||||||
|
* The first POST omits [code] (sends just the email); the second
|
||||||
|
* POST includes the code the user received by email.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class KratosRecoveryBody(
|
||||||
|
val method: String = "code",
|
||||||
|
val email: String? = null,
|
||||||
|
val code: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Body for submitting the `code` method to a verification flow.
|
||||||
|
* As with recovery: first POST sends the email, second sends the code.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class KratosVerificationBody(
|
||||||
|
val method: String = "code",
|
||||||
|
val email: String? = null,
|
||||||
|
val code: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Body for updating an identity's password from within a settings flow
|
||||||
|
* (used after a recovery flow hands the client a privileged session).
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class KratosSettingsPasswordBody(
|
||||||
|
val method: String = "password",
|
||||||
|
val password: String,
|
||||||
|
)
|
||||||
@@ -10,8 +10,13 @@ import kotlinx.serialization.Serializable
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class ResidenceType(
|
data class ResidenceType(
|
||||||
val id: Int,
|
val id: Int,
|
||||||
val name: String
|
val name: String,
|
||||||
)
|
@SerialName("display_name") val displayNameLocalized: String = ""
|
||||||
|
) {
|
||||||
|
/** Localized label for the current locale; falls back to [name]. */
|
||||||
|
val displayName: String
|
||||||
|
get() = displayNameLocalized.ifBlank { name }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Task frequency lookup - matching Go API TaskFrequencyResponse
|
* Task frequency lookup - matching Go API TaskFrequencyResponse
|
||||||
@@ -20,12 +25,13 @@ data class ResidenceType(
|
|||||||
data class TaskFrequency(
|
data class TaskFrequency(
|
||||||
val id: Int,
|
val id: Int,
|
||||||
val name: String,
|
val name: String,
|
||||||
|
@SerialName("display_name") val displayNameLocalized: String = "",
|
||||||
val days: Int? = null,
|
val days: Int? = null,
|
||||||
@SerialName("display_order") val displayOrder: Int = 0
|
@SerialName("display_order") val displayOrder: Int = 0
|
||||||
) {
|
) {
|
||||||
// Helper for display
|
/** Localized label for the current locale; falls back to [name]. */
|
||||||
val displayName: String
|
val displayName: String
|
||||||
get() = name
|
get() = displayNameLocalized.ifBlank { name }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,13 +41,14 @@ data class TaskFrequency(
|
|||||||
data class TaskPriority(
|
data class TaskPriority(
|
||||||
val id: Int,
|
val id: Int,
|
||||||
val name: String,
|
val name: String,
|
||||||
|
@SerialName("display_name") val displayNameLocalized: String = "",
|
||||||
val level: Int = 0,
|
val level: Int = 0,
|
||||||
val color: String = "",
|
val color: String = "",
|
||||||
@SerialName("display_order") val displayOrder: Int = 0
|
@SerialName("display_order") val displayOrder: Int = 0
|
||||||
) {
|
) {
|
||||||
// Helper for display
|
/** Localized label for the current locale; falls back to [name]. */
|
||||||
val displayName: String
|
val displayName: String
|
||||||
get() = name
|
get() = displayNameLocalized.ifBlank { name }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -51,11 +58,16 @@ data class TaskPriority(
|
|||||||
data class TaskCategory(
|
data class TaskCategory(
|
||||||
val id: Int,
|
val id: Int,
|
||||||
val name: String,
|
val name: String,
|
||||||
|
@SerialName("display_name") val displayNameLocalized: String = "",
|
||||||
val description: String = "",
|
val description: String = "",
|
||||||
val icon: String = "",
|
val icon: String = "",
|
||||||
val color: String = "",
|
val color: String = "",
|
||||||
@SerialName("display_order") val displayOrder: Int = 0
|
@SerialName("display_order") val displayOrder: Int = 0
|
||||||
)
|
) {
|
||||||
|
/** Localized label for the current locale; falls back to [name]. */
|
||||||
|
val displayName: String
|
||||||
|
get() = displayNameLocalized.ifBlank { name }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contractor specialty lookup
|
* Contractor specialty lookup
|
||||||
@@ -64,9 +76,25 @@ data class TaskCategory(
|
|||||||
data class ContractorSpecialty(
|
data class ContractorSpecialty(
|
||||||
val id: Int,
|
val id: Int,
|
||||||
val name: String,
|
val name: String,
|
||||||
|
@SerialName("display_name") val displayNameLocalized: String = "",
|
||||||
val description: String? = null,
|
val description: String? = null,
|
||||||
val icon: String? = null,
|
val icon: String? = null,
|
||||||
@SerialName("display_order") val displayOrder: Int = 0
|
@SerialName("display_order") val displayOrder: Int = 0
|
||||||
|
) {
|
||||||
|
/** Localized label for the current locale; falls back to [name]. */
|
||||||
|
val displayName: String
|
||||||
|
get() = displayNameLocalized.ifBlank { name }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A selectable home-profile field option (heating type, roof type, etc.),
|
||||||
|
* served localized by the backend. [value] is the stable code stored on the
|
||||||
|
* residence; [displayName] is the localized label.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class HomeProfileOption(
|
||||||
|
val value: String,
|
||||||
|
@SerialName("display_name") val displayName: String
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -103,7 +131,10 @@ data class SeededDataResponse(
|
|||||||
@SerialName("task_priorities") val taskPriorities: List<TaskPriority>,
|
@SerialName("task_priorities") val taskPriorities: List<TaskPriority>,
|
||||||
@SerialName("task_frequencies") val taskFrequencies: List<TaskFrequency>,
|
@SerialName("task_frequencies") val taskFrequencies: List<TaskFrequency>,
|
||||||
@SerialName("contractor_specialties") val contractorSpecialties: List<ContractorSpecialty>,
|
@SerialName("contractor_specialties") val contractorSpecialties: List<ContractorSpecialty>,
|
||||||
@SerialName("task_templates") val taskTemplates: TaskTemplatesGroupedResponse
|
@SerialName("task_templates") val taskTemplates: TaskTemplatesGroupedResponse,
|
||||||
|
@SerialName("home_profile_options") val homeProfileOptions: Map<String, List<HomeProfileOption>> = emptyMap(),
|
||||||
|
@SerialName("document_types") val documentTypes: List<HomeProfileOption> = emptyList(),
|
||||||
|
@SerialName("document_categories") val documentCategories: List<HomeProfileOption> = emptyList()
|
||||||
)
|
)
|
||||||
|
|
||||||
// Legacy wrapper responses for backward compatibility
|
// Legacy wrapper responses for backward compatibility
|
||||||
|
|||||||
@@ -34,15 +34,20 @@ data class PresignUploadRequest(
|
|||||||
/**
|
/**
|
||||||
* Presigned upload session — response from POST /api/uploads/presign.
|
* Presigned upload session — response from POST /api/uploads/presign.
|
||||||
*
|
*
|
||||||
* The client uses [uploadUrl] + [fields] to perform a multipart/form-data
|
* The client makes one PUT request to [uploadUrl] with the raw object
|
||||||
* POST directly to B2, then passes [id] back in the upload_ids[] field of
|
* bytes as the body and [headers] as the request headers. On success,
|
||||||
* the next /api/task-completions/ or /api/documents/ create call.
|
* pass [id] back in the upload_ids[] field of the next
|
||||||
|
* /api/task-completions/ or /api/documents/ create call.
|
||||||
|
*
|
||||||
|
* PUT (not POST) because B2's S3-compatible endpoint does not implement
|
||||||
|
* the S3 POST Object form upload (returns HTTP 501).
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
data class PresignUploadResponse(
|
data class PresignUploadResponse(
|
||||||
val id: Int,
|
val id: Int,
|
||||||
@SerialName("upload_url") val uploadUrl: String,
|
@SerialName("upload_url") val uploadUrl: String,
|
||||||
val fields: Map<String, String>,
|
val method: String = "PUT",
|
||||||
|
val headers: Map<String, String> = emptyMap(),
|
||||||
val key: String,
|
val key: String,
|
||||||
@SerialName("expires_at") val expiresAt: String
|
@SerialName("expires_at") val expiresAt: String
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.tt.honeyDue.models
|
package com.tt.honeyDue.models
|
||||||
|
|
||||||
|
import com.tt.honeyDue.i18n.ClientStrings
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@@ -26,13 +27,13 @@ data class TaskTemplate(
|
|||||||
* Human-readable frequency display
|
* Human-readable frequency display
|
||||||
*/
|
*/
|
||||||
val frequencyDisplay: String
|
val frequencyDisplay: String
|
||||||
get() = frequency?.displayName ?: "One time"
|
get() = frequency?.displayName ?: ClientStrings.t("task.frequency.one_time")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Category name for display
|
* Category name for display
|
||||||
*/
|
*/
|
||||||
val categoryName: String
|
val categoryName: String
|
||||||
get() = category?.name ?: "Uncategorized"
|
get() = category?.name ?: ClientStrings.t("task.category.uncategorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -85,7 +85,13 @@ data class AuthResponse(
|
|||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Token refresh response - returned by POST /api/auth/refresh/
|
* Token refresh response.
|
||||||
|
*
|
||||||
|
* Identity is owned by Ory Kratos. Native Kratos session tokens are
|
||||||
|
* long-lived and not rotated — there is no refresh endpoint. This type is
|
||||||
|
* retained as the return shape of [com.tt.honeyDue.network.AuthApi.refreshToken],
|
||||||
|
* which now re-validates the session via Kratos `/sessions/whoami` and echoes
|
||||||
|
* the same (unchanged) token back when the session is still active.
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
data class TokenRefreshResponse(
|
data class TokenRefreshResponse(
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.tt.honeyDue.network
|
package com.tt.honeyDue.network
|
||||||
|
|
||||||
|
import com.tt.honeyDue.i18n.ClientStrings
|
||||||
|
|
||||||
import com.tt.honeyDue.data.DataManager
|
import com.tt.honeyDue.data.DataManager
|
||||||
import com.tt.honeyDue.models.*
|
import com.tt.honeyDue.models.*
|
||||||
import com.tt.honeyDue.network.*
|
import com.tt.honeyDue.network.*
|
||||||
@@ -54,7 +56,7 @@ object APILayer {
|
|||||||
* Call this when app comes to foreground or when limits might have changed.
|
* Call this when app comes to foreground or when limits might have changed.
|
||||||
*/
|
*/
|
||||||
suspend fun refreshSubscriptionStatus(): ApiResult<SubscriptionStatus> {
|
suspend fun refreshSubscriptionStatus(): ApiResult<SubscriptionStatus> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
|
|
||||||
println("🔄 [APILayer] Force refreshing subscription status from backend...")
|
println("🔄 [APILayer] Force refreshing subscription status from backend...")
|
||||||
val result = subscriptionApi.getSubscriptionStatus(token)
|
val result = subscriptionApi.getSubscriptionStatus(token)
|
||||||
@@ -184,7 +186,7 @@ object APILayer {
|
|||||||
DataManager.markLookupsInitialized()
|
DataManager.markLookupsInitialized()
|
||||||
return ApiResult.Success(Unit)
|
return ApiResult.Success(Unit)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
return ApiResult.Error("Failed to initialize lookups: ${e.message}")
|
return ApiResult.Error(ClientStrings.t("err.api.init_lookups_failed", e.message ?: ""))
|
||||||
} finally {
|
} finally {
|
||||||
lookupsInitMutex.unlock()
|
lookupsInitMutex.unlock()
|
||||||
}
|
}
|
||||||
@@ -251,10 +253,10 @@ object APILayer {
|
|||||||
DataManager.markLookupsInitialized()
|
DataManager.markLookupsInitialized()
|
||||||
return ApiResult.Success(Unit)
|
return ApiResult.Success(Unit)
|
||||||
} else if (staticDataResult is ApiResult.Error) {
|
} else if (staticDataResult is ApiResult.Error) {
|
||||||
return ApiResult.Error("Failed to load lookups: ${staticDataResult.message}")
|
return ApiResult.Error(ClientStrings.t("err.api.load_lookups_failed", staticDataResult.message))
|
||||||
}
|
}
|
||||||
|
|
||||||
return ApiResult.Error("Unknown error loading lookups")
|
return ApiResult.Error(ClientStrings.t("err.api.unknown_loading_lookups"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -268,7 +270,7 @@ object APILayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = lookupsApi.getResidenceTypes(token)
|
val result = lookupsApi.getResidenceTypes(token)
|
||||||
|
|
||||||
if (result is ApiResult.Success) {
|
if (result is ApiResult.Success) {
|
||||||
@@ -289,7 +291,7 @@ object APILayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = lookupsApi.getTaskFrequencies(token)
|
val result = lookupsApi.getTaskFrequencies(token)
|
||||||
|
|
||||||
if (result is ApiResult.Success) {
|
if (result is ApiResult.Success) {
|
||||||
@@ -310,7 +312,7 @@ object APILayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = lookupsApi.getTaskPriorities(token)
|
val result = lookupsApi.getTaskPriorities(token)
|
||||||
|
|
||||||
if (result is ApiResult.Success) {
|
if (result is ApiResult.Success) {
|
||||||
@@ -331,7 +333,7 @@ object APILayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = lookupsApi.getTaskCategories(token)
|
val result = lookupsApi.getTaskCategories(token)
|
||||||
|
|
||||||
if (result is ApiResult.Success) {
|
if (result is ApiResult.Success) {
|
||||||
@@ -352,7 +354,7 @@ object APILayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = lookupsApi.getContractorSpecialties(token)
|
val result = lookupsApi.getContractorSpecialties(token)
|
||||||
|
|
||||||
if (result is ApiResult.Success) {
|
if (result is ApiResult.Success) {
|
||||||
@@ -374,7 +376,7 @@ object APILayer {
|
|||||||
println("[APILayer] CACHE MISS: residences (forceRefresh=$forceRefresh)")
|
println("[APILayer] CACHE MISS: residences (forceRefresh=$forceRefresh)")
|
||||||
|
|
||||||
// Fetch from API
|
// Fetch from API
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = residenceApi.getResidences(token)
|
val result = residenceApi.getResidences(token)
|
||||||
|
|
||||||
// Update DataManager on success
|
// Update DataManager on success
|
||||||
@@ -397,7 +399,7 @@ object APILayer {
|
|||||||
println("[APILayer] CACHE MISS: myResidences (forceRefresh=$forceRefresh)")
|
println("[APILayer] CACHE MISS: myResidences (forceRefresh=$forceRefresh)")
|
||||||
|
|
||||||
// Fetch from API
|
// Fetch from API
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = residenceApi.getMyResidences(token)
|
val result = residenceApi.getMyResidences(token)
|
||||||
|
|
||||||
// Update DataManager on success
|
// Update DataManager on success
|
||||||
@@ -422,7 +424,7 @@ object APILayer {
|
|||||||
* cache without requiring a manual pull-to-refresh.
|
* cache without requiring a manual pull-to-refresh.
|
||||||
*/
|
*/
|
||||||
suspend fun acceptResidenceInvite(residenceId: Int): ApiResult<Unit> {
|
suspend fun acceptResidenceInvite(residenceId: Int): ApiResult<Unit> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = residenceApi.acceptResidenceInvite(token, residenceId)
|
val result = residenceApi.acceptResidenceInvite(token, residenceId)
|
||||||
if (result is ApiResult.Success) {
|
if (result is ApiResult.Success) {
|
||||||
// Residence list may have changed — force a refresh so any
|
// Residence list may have changed — force a refresh so any
|
||||||
@@ -438,7 +440,7 @@ object APILayer {
|
|||||||
* client-side; the next app-open will re-query the server anyway.
|
* client-side; the next app-open will re-query the server anyway.
|
||||||
*/
|
*/
|
||||||
suspend fun declineResidenceInvite(residenceId: Int): ApiResult<Unit> {
|
suspend fun declineResidenceInvite(residenceId: Int): ApiResult<Unit> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
return residenceApi.declineResidenceInvite(token, residenceId)
|
return residenceApi.declineResidenceInvite(token, residenceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -462,7 +464,7 @@ object APILayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fetch from API
|
// Fetch from API
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = residenceApi.getResidence(token, id)
|
val result = residenceApi.getResidence(token, id)
|
||||||
|
|
||||||
// Update DataManager on success
|
// Update DataManager on success
|
||||||
@@ -486,7 +488,7 @@ object APILayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = residenceApi.getSummary(token)
|
val result = residenceApi.getSummary(token)
|
||||||
|
|
||||||
if (result is ApiResult.Success) {
|
if (result is ApiResult.Success) {
|
||||||
@@ -497,7 +499,7 @@ object APILayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun createResidence(request: ResidenceCreateRequest): ApiResult<ResidenceResponse> {
|
suspend fun createResidence(request: ResidenceCreateRequest): ApiResult<ResidenceResponse> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = residenceApi.createResidence(token, request)
|
val result = residenceApi.createResidence(token, request)
|
||||||
|
|
||||||
// Extract summary and update local cache
|
// Extract summary and update local cache
|
||||||
@@ -509,12 +511,12 @@ object APILayer {
|
|||||||
|
|
||||||
return when (result) {
|
return when (result) {
|
||||||
is ApiResult.Error -> result
|
is ApiResult.Error -> result
|
||||||
else -> ApiResult.Error("Unknown error")
|
else -> ApiResult.Error(ClientStrings.t("err.unknown"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateResidence(id: Int, request: ResidenceCreateRequest): ApiResult<ResidenceResponse> {
|
suspend fun updateResidence(id: Int, request: ResidenceCreateRequest): ApiResult<ResidenceResponse> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = residenceApi.updateResidence(token, id, request)
|
val result = residenceApi.updateResidence(token, id, request)
|
||||||
|
|
||||||
// Extract summary and update local cache
|
// Extract summary and update local cache
|
||||||
@@ -526,12 +528,12 @@ object APILayer {
|
|||||||
|
|
||||||
return when (result) {
|
return when (result) {
|
||||||
is ApiResult.Error -> result
|
is ApiResult.Error -> result
|
||||||
else -> ApiResult.Error("Unknown error")
|
else -> ApiResult.Error(ClientStrings.t("err.unknown"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteResidence(id: Int): ApiResult<Unit> {
|
suspend fun deleteResidence(id: Int): ApiResult<Unit> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = residenceApi.deleteResidence(token, id)
|
val result = residenceApi.deleteResidence(token, id)
|
||||||
|
|
||||||
// Extract summary and update local cache
|
// Extract summary and update local cache
|
||||||
@@ -543,17 +545,17 @@ object APILayer {
|
|||||||
|
|
||||||
return when (result) {
|
return when (result) {
|
||||||
is ApiResult.Error -> result
|
is ApiResult.Error -> result
|
||||||
else -> ApiResult.Error("Unknown error")
|
else -> ApiResult.Error(ClientStrings.t("err.unknown"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun generateTasksReport(residenceId: Int, email: String? = null): ApiResult<GenerateReportResponse> {
|
suspend fun generateTasksReport(residenceId: Int, email: String? = null): ApiResult<GenerateReportResponse> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
return residenceApi.generateTasksReport(token, residenceId, email)
|
return residenceApi.generateTasksReport(token, residenceId, email)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun joinWithCode(code: String): ApiResult<JoinResidenceResponse> {
|
suspend fun joinWithCode(code: String): ApiResult<JoinResidenceResponse> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = residenceApi.joinWithCode(token, code)
|
val result = residenceApi.joinWithCode(token, code)
|
||||||
|
|
||||||
// Extract summary and update local cache
|
// Extract summary and update local cache
|
||||||
@@ -566,27 +568,27 @@ object APILayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getResidenceUsers(residenceId: Int): ApiResult<ResidenceUsersResponse> {
|
suspend fun getResidenceUsers(residenceId: Int): ApiResult<ResidenceUsersResponse> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
return residenceApi.getResidenceUsers(token, residenceId)
|
return residenceApi.getResidenceUsers(token, residenceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getShareCode(residenceId: Int): ApiResult<ShareCodeResponse> {
|
suspend fun getShareCode(residenceId: Int): ApiResult<ShareCodeResponse> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
return residenceApi.getShareCode(token, residenceId)
|
return residenceApi.getShareCode(token, residenceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun generateShareCode(residenceId: Int): ApiResult<GenerateShareCodeResponse> {
|
suspend fun generateShareCode(residenceId: Int): ApiResult<GenerateShareCodeResponse> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
return residenceApi.generateShareCode(token, residenceId)
|
return residenceApi.generateShareCode(token, residenceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun generateSharePackage(residenceId: Int): ApiResult<SharedResidence> {
|
suspend fun generateSharePackage(residenceId: Int): ApiResult<SharedResidence> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
return residenceApi.generateSharePackage(token, residenceId)
|
return residenceApi.generateSharePackage(token, residenceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun removeUser(residenceId: Int, userId: Int): ApiResult<RemoveUserResponse> {
|
suspend fun removeUser(residenceId: Int, userId: Int): ApiResult<RemoveUserResponse> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
return residenceApi.removeUser(token, residenceId, userId)
|
return residenceApi.removeUser(token, residenceId, userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -604,7 +606,7 @@ object APILayer {
|
|||||||
println("[APILayer] CACHE MISS: tasks (forceRefresh=$forceRefresh)")
|
println("[APILayer] CACHE MISS: tasks (forceRefresh=$forceRefresh)")
|
||||||
|
|
||||||
// Fetch from API
|
// Fetch from API
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = taskApi.getTasks(token)
|
val result = taskApi.getTasks(token)
|
||||||
|
|
||||||
// Update DataManager on success
|
// Update DataManager on success
|
||||||
@@ -629,12 +631,12 @@ object APILayer {
|
|||||||
if (allTasksResult is ApiResult.Error) return allTasksResult
|
if (allTasksResult is ApiResult.Error) return allTasksResult
|
||||||
|
|
||||||
val filtered = DataManager.getTasksForResidence(residenceId)
|
val filtered = DataManager.getTasksForResidence(residenceId)
|
||||||
?: return ApiResult.Error("Tasks unavailable", 0)
|
?: return ApiResult.Error(ClientStrings.t("err.api.tasks_unavailable"), 0)
|
||||||
return ApiResult.Success(filtered)
|
return ApiResult.Success(filtered)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun createTask(request: TaskCreateRequest): ApiResult<TaskResponse> {
|
suspend fun createTask(request: TaskCreateRequest): ApiResult<TaskResponse> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = taskApi.createTask(token, request)
|
val result = taskApi.createTask(token, request)
|
||||||
|
|
||||||
// Extract summary and update local cache with new task
|
// Extract summary and update local cache with new task
|
||||||
@@ -647,7 +649,7 @@ object APILayer {
|
|||||||
|
|
||||||
return when (result) {
|
return when (result) {
|
||||||
is ApiResult.Error -> result
|
is ApiResult.Error -> result
|
||||||
else -> ApiResult.Error("Unknown error")
|
else -> ApiResult.Error(ClientStrings.t("err.unknown"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -664,7 +666,7 @@ object APILayer {
|
|||||||
* after onboarding), silently dropping the new tasks from cache.
|
* after onboarding), silently dropping the new tasks from cache.
|
||||||
*/
|
*/
|
||||||
suspend fun bulkCreateTasks(request: BulkCreateTasksRequest): ApiResult<BulkCreateTasksResponse> {
|
suspend fun bulkCreateTasks(request: BulkCreateTasksRequest): ApiResult<BulkCreateTasksResponse> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = taskApi.bulkCreateTasks(token, request)
|
val result = taskApi.bulkCreateTasks(token, request)
|
||||||
|
|
||||||
if (result is ApiResult.Success) {
|
if (result is ApiResult.Success) {
|
||||||
@@ -677,7 +679,7 @@ object APILayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateTask(id: Int, request: TaskCreateRequest): ApiResult<TaskResponse> {
|
suspend fun updateTask(id: Int, request: TaskCreateRequest): ApiResult<TaskResponse> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = taskApi.updateTask(token, id, request)
|
val result = taskApi.updateTask(token, id, request)
|
||||||
|
|
||||||
// Extract summary and update local cache with modified task
|
// Extract summary and update local cache with modified task
|
||||||
@@ -690,12 +692,12 @@ object APILayer {
|
|||||||
|
|
||||||
return when (result) {
|
return when (result) {
|
||||||
is ApiResult.Error -> result
|
is ApiResult.Error -> result
|
||||||
else -> ApiResult.Error("Unknown error")
|
else -> ApiResult.Error(ClientStrings.t("err.unknown"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun cancelTask(taskId: Int): ApiResult<TaskResponse> {
|
suspend fun cancelTask(taskId: Int): ApiResult<TaskResponse> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = taskApi.cancelTask(token, taskId)
|
val result = taskApi.cancelTask(token, taskId)
|
||||||
|
|
||||||
if (result is ApiResult.Success) {
|
if (result is ApiResult.Success) {
|
||||||
@@ -706,12 +708,12 @@ object APILayer {
|
|||||||
|
|
||||||
return when (result) {
|
return when (result) {
|
||||||
is ApiResult.Error -> result
|
is ApiResult.Error -> result
|
||||||
else -> ApiResult.Error("Unknown error")
|
else -> ApiResult.Error(ClientStrings.t("err.unknown"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun uncancelTask(taskId: Int): ApiResult<TaskResponse> {
|
suspend fun uncancelTask(taskId: Int): ApiResult<TaskResponse> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = taskApi.uncancelTask(token, taskId)
|
val result = taskApi.uncancelTask(token, taskId)
|
||||||
|
|
||||||
if (result is ApiResult.Success) {
|
if (result is ApiResult.Success) {
|
||||||
@@ -722,12 +724,12 @@ object APILayer {
|
|||||||
|
|
||||||
return when (result) {
|
return when (result) {
|
||||||
is ApiResult.Error -> result
|
is ApiResult.Error -> result
|
||||||
else -> ApiResult.Error("Unknown error")
|
else -> ApiResult.Error(ClientStrings.t("err.unknown"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun markInProgress(taskId: Int): ApiResult<TaskResponse> {
|
suspend fun markInProgress(taskId: Int): ApiResult<TaskResponse> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = taskApi.markInProgress(token, taskId)
|
val result = taskApi.markInProgress(token, taskId)
|
||||||
|
|
||||||
if (result is ApiResult.Success) {
|
if (result is ApiResult.Success) {
|
||||||
@@ -738,12 +740,12 @@ object APILayer {
|
|||||||
|
|
||||||
return when (result) {
|
return when (result) {
|
||||||
is ApiResult.Error -> result
|
is ApiResult.Error -> result
|
||||||
else -> ApiResult.Error("Unknown error")
|
else -> ApiResult.Error(ClientStrings.t("err.unknown"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun clearInProgress(taskId: Int): ApiResult<TaskResponse> {
|
suspend fun clearInProgress(taskId: Int): ApiResult<TaskResponse> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = taskApi.clearInProgress(token, taskId)
|
val result = taskApi.clearInProgress(token, taskId)
|
||||||
|
|
||||||
if (result is ApiResult.Success) {
|
if (result is ApiResult.Success) {
|
||||||
@@ -754,12 +756,12 @@ object APILayer {
|
|||||||
|
|
||||||
return when (result) {
|
return when (result) {
|
||||||
is ApiResult.Error -> result
|
is ApiResult.Error -> result
|
||||||
else -> ApiResult.Error("Unknown error")
|
else -> ApiResult.Error(ClientStrings.t("err.unknown"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun archiveTask(taskId: Int): ApiResult<TaskResponse> {
|
suspend fun archiveTask(taskId: Int): ApiResult<TaskResponse> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = taskApi.archiveTask(token, taskId)
|
val result = taskApi.archiveTask(token, taskId)
|
||||||
|
|
||||||
if (result is ApiResult.Success) {
|
if (result is ApiResult.Success) {
|
||||||
@@ -770,12 +772,12 @@ object APILayer {
|
|||||||
|
|
||||||
return when (result) {
|
return when (result) {
|
||||||
is ApiResult.Error -> result
|
is ApiResult.Error -> result
|
||||||
else -> ApiResult.Error("Unknown error")
|
else -> ApiResult.Error(ClientStrings.t("err.unknown"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun unarchiveTask(taskId: Int): ApiResult<TaskResponse> {
|
suspend fun unarchiveTask(taskId: Int): ApiResult<TaskResponse> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = taskApi.unarchiveTask(token, taskId)
|
val result = taskApi.unarchiveTask(token, taskId)
|
||||||
|
|
||||||
if (result is ApiResult.Success) {
|
if (result is ApiResult.Success) {
|
||||||
@@ -786,12 +788,12 @@ object APILayer {
|
|||||||
|
|
||||||
return when (result) {
|
return when (result) {
|
||||||
is ApiResult.Error -> result
|
is ApiResult.Error -> result
|
||||||
else -> ApiResult.Error("Unknown error")
|
else -> ApiResult.Error(ClientStrings.t("err.unknown"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun createTaskCompletion(request: TaskCompletionCreateRequest): ApiResult<TaskCompletionResponse> {
|
suspend fun createTaskCompletion(request: TaskCompletionCreateRequest): ApiResult<TaskCompletionResponse> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = taskCompletionApi.createCompletion(token, request)
|
val result = taskCompletionApi.createCompletion(token, request)
|
||||||
|
|
||||||
if (result is ApiResult.Success) {
|
if (result is ApiResult.Success) {
|
||||||
@@ -806,7 +808,7 @@ object APILayer {
|
|||||||
|
|
||||||
return when (result) {
|
return when (result) {
|
||||||
is ApiResult.Error -> result
|
is ApiResult.Error -> result
|
||||||
else -> ApiResult.Error("Unknown error")
|
else -> ApiResult.Error(ClientStrings.t("err.unknown"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -814,7 +816,7 @@ object APILayer {
|
|||||||
* Get all completions for a specific task
|
* Get all completions for a specific task
|
||||||
*/
|
*/
|
||||||
suspend fun getTaskCompletions(taskId: Int): ApiResult<List<TaskCompletionResponse>> {
|
suspend fun getTaskCompletions(taskId: Int): ApiResult<List<TaskCompletionResponse>> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = taskApi.getTaskCompletions(token, taskId)
|
val result = taskApi.getTaskCompletions(token, taskId)
|
||||||
if (result is ApiResult.Success) {
|
if (result is ApiResult.Success) {
|
||||||
DataManager.setTaskCompletions(taskId, result.data)
|
DataManager.setTaskCompletions(taskId, result.data)
|
||||||
@@ -848,7 +850,7 @@ object APILayer {
|
|||||||
println("[APILayer] CACHE MISS: documents (forceRefresh=$forceRefresh, hasFilters=$hasFilters)")
|
println("[APILayer] CACHE MISS: documents (forceRefresh=$forceRefresh, hasFilters=$hasFilters)")
|
||||||
|
|
||||||
// Fetch from API
|
// Fetch from API
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = documentApi.getDocuments(
|
val result = documentApi.getDocuments(
|
||||||
token, residenceId, documentType, category, contractorId,
|
token, residenceId, documentType, category, contractorId,
|
||||||
isActive, expiringSoon, tags, search
|
isActive, expiringSoon, tags, search
|
||||||
@@ -872,7 +874,7 @@ object APILayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fetch from API
|
// Fetch from API
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = documentApi.getDocument(token, id)
|
val result = documentApi.getDocument(token, id)
|
||||||
|
|
||||||
// Update DataManager on success
|
// Update DataManager on success
|
||||||
@@ -912,7 +914,7 @@ object APILayer {
|
|||||||
fileNamesList: List<String>? = null,
|
fileNamesList: List<String>? = null,
|
||||||
mimeTypesList: List<String>? = null
|
mimeTypesList: List<String>? = null
|
||||||
): ApiResult<Document> {
|
): ApiResult<Document> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = documentApi.createDocument(
|
val result = documentApi.createDocument(
|
||||||
token, title, documentType, residenceId, description, category,
|
token, title, documentType, residenceId, description, category,
|
||||||
tags, notes, contractorId, isActive, itemName, modelNumber,
|
tags, notes, contractorId, isActive, itemName, modelNumber,
|
||||||
@@ -951,7 +953,7 @@ object APILayer {
|
|||||||
startDate: String? = null,
|
startDate: String? = null,
|
||||||
endDate: String? = null
|
endDate: String? = null
|
||||||
): ApiResult<Document> {
|
): ApiResult<Document> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = documentApi.updateDocument(
|
val result = documentApi.updateDocument(
|
||||||
token, id, title, documentType, description, category, tags, notes,
|
token, id, title, documentType, description, category, tags, notes,
|
||||||
contractorId, isActive, itemName, modelNumber, serialNumber, provider,
|
contractorId, isActive, itemName, modelNumber, serialNumber, provider,
|
||||||
@@ -968,7 +970,7 @@ object APILayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteDocument(id: Int): ApiResult<Unit> {
|
suspend fun deleteDocument(id: Int): ApiResult<Unit> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = documentApi.deleteDocument(token, id)
|
val result = documentApi.deleteDocument(token, id)
|
||||||
|
|
||||||
// Update DataManager on success
|
// Update DataManager on success
|
||||||
@@ -986,7 +988,7 @@ object APILayer {
|
|||||||
mimeType: String,
|
mimeType: String,
|
||||||
caption: String? = null
|
caption: String? = null
|
||||||
): ApiResult<Document> {
|
): ApiResult<Document> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = documentApi.uploadDocumentImage(token, documentId, imageBytes, fileName, mimeType, caption)
|
val result = documentApi.uploadDocumentImage(token, documentId, imageBytes, fileName, mimeType, caption)
|
||||||
if (result is ApiResult.Success) {
|
if (result is ApiResult.Success) {
|
||||||
DataManager.updateDocument(result.data)
|
DataManager.updateDocument(result.data)
|
||||||
@@ -995,7 +997,7 @@ object APILayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteDocumentImage(documentId: Int, imageId: Int): ApiResult<Document> {
|
suspend fun deleteDocumentImage(documentId: Int, imageId: Int): ApiResult<Document> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = documentApi.deleteDocumentImage(token, documentId, imageId)
|
val result = documentApi.deleteDocumentImage(token, documentId, imageId)
|
||||||
if (result is ApiResult.Success) {
|
if (result is ApiResult.Success) {
|
||||||
DataManager.updateDocument(result.data)
|
DataManager.updateDocument(result.data)
|
||||||
@@ -1004,7 +1006,7 @@ object APILayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun downloadDocument(url: String): ApiResult<ByteArray> {
|
suspend fun downloadDocument(url: String): ApiResult<ByteArray> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
return documentApi.downloadDocument(token, url)
|
return documentApi.downloadDocument(token, url)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1026,7 +1028,7 @@ object APILayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fetch from API
|
// Fetch from API
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = contractorApi.getContractors(token, specialty, isFavorite, isActive, search)
|
val result = contractorApi.getContractors(token, specialty, isFavorite, isActive, search)
|
||||||
|
|
||||||
// Update DataManager on success (only for unfiltered results)
|
// Update DataManager on success (only for unfiltered results)
|
||||||
@@ -1039,7 +1041,7 @@ object APILayer {
|
|||||||
|
|
||||||
suspend fun getContractor(id: Int, forceRefresh: Boolean = false): ApiResult<Contractor> {
|
suspend fun getContractor(id: Int, forceRefresh: Boolean = false): ApiResult<Contractor> {
|
||||||
// Fetch from API (summaries don't have full detail, always fetch)
|
// Fetch from API (summaries don't have full detail, always fetch)
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = contractorApi.getContractor(token, id)
|
val result = contractorApi.getContractor(token, id)
|
||||||
|
|
||||||
// Update the summary in DataManager on success
|
// Update the summary in DataManager on success
|
||||||
@@ -1052,7 +1054,7 @@ object APILayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun createContractor(request: ContractorCreateRequest): ApiResult<Contractor> {
|
suspend fun createContractor(request: ContractorCreateRequest): ApiResult<Contractor> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = contractorApi.createContractor(token, request)
|
val result = contractorApi.createContractor(token, request)
|
||||||
|
|
||||||
// Update DataManager on success
|
// Update DataManager on success
|
||||||
@@ -1064,7 +1066,7 @@ object APILayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateContractor(id: Int, request: ContractorUpdateRequest): ApiResult<Contractor> {
|
suspend fun updateContractor(id: Int, request: ContractorUpdateRequest): ApiResult<Contractor> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = contractorApi.updateContractor(token, id, request)
|
val result = contractorApi.updateContractor(token, id, request)
|
||||||
|
|
||||||
// Update DataManager on success
|
// Update DataManager on success
|
||||||
@@ -1076,7 +1078,7 @@ object APILayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteContractor(id: Int): ApiResult<Unit> {
|
suspend fun deleteContractor(id: Int): ApiResult<Unit> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = contractorApi.deleteContractor(token, id)
|
val result = contractorApi.deleteContractor(token, id)
|
||||||
|
|
||||||
// Update DataManager on success
|
// Update DataManager on success
|
||||||
@@ -1088,7 +1090,7 @@ object APILayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun toggleFavorite(id: Int): ApiResult<Contractor> {
|
suspend fun toggleFavorite(id: Int): ApiResult<Contractor> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = contractorApi.toggleFavorite(token, id)
|
val result = contractorApi.toggleFavorite(token, id)
|
||||||
|
|
||||||
// Update DataManager on success
|
// Update DataManager on success
|
||||||
@@ -1110,7 +1112,7 @@ object APILayer {
|
|||||||
|
|
||||||
// If cache is empty or stale, fetch all contractors first to populate cache
|
// If cache is empty or stale, fetch all contractors first to populate cache
|
||||||
if (DataManager.contractors.value.isEmpty() || !DataManager.isCacheValid(DataManager.contractorsCacheTime)) {
|
if (DataManager.contractors.value.isEmpty() || !DataManager.isCacheValid(DataManager.contractorsCacheTime)) {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = contractorApi.getContractors(token, null, null, null, null)
|
val result = contractorApi.getContractors(token, null, null, null, null)
|
||||||
if (result is ApiResult.Success) {
|
if (result is ApiResult.Success) {
|
||||||
DataManager.setContractors(result.data)
|
DataManager.setContractors(result.data)
|
||||||
@@ -1172,7 +1174,7 @@ object APILayer {
|
|||||||
initializeLookups()
|
initializeLookups()
|
||||||
return DataManager.taskTemplatesGrouped.value?.let {
|
return DataManager.taskTemplatesGrouped.value?.let {
|
||||||
ApiResult.Success(it)
|
ApiResult.Success(it)
|
||||||
} ?: ApiResult.Error("Failed to load task templates")
|
} ?: ApiResult.Error(ClientStrings.t("err.api.failed_load_task_templates"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1220,14 +1222,14 @@ object APILayer {
|
|||||||
val cached = DataManager.taskTemplates.value.find { it.id == id }
|
val cached = DataManager.taskTemplates.value.find { it.id == id }
|
||||||
return cached?.let {
|
return cached?.let {
|
||||||
ApiResult.Success(it)
|
ApiResult.Success(it)
|
||||||
} ?: ApiResult.Error("Task template not found")
|
} ?: ApiResult.Error(ClientStrings.t("err.api.task_template_not_found"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get personalized task suggestions for a residence based on its home profile.
|
* Get personalized task suggestions for a residence based on its home profile.
|
||||||
*/
|
*/
|
||||||
suspend fun getTaskSuggestions(residenceId: Int): ApiResult<TaskSuggestionsResponse> {
|
suspend fun getTaskSuggestions(residenceId: Int): ApiResult<TaskSuggestionsResponse> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
return taskTemplateApi.getTaskSuggestions(token, residenceId)
|
return taskTemplateApi.getTaskSuggestions(token, residenceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1266,7 +1268,7 @@ object APILayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun logout(): ApiResult<Unit> {
|
suspend fun logout(): ApiResult<Unit> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = authApi.logout(token)
|
val result = authApi.logout(token)
|
||||||
|
|
||||||
// Clear DataManager on logout (success or failure)
|
// Clear DataManager on logout (success or failure)
|
||||||
@@ -1276,22 +1278,26 @@ object APILayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refresh the current auth token.
|
* Re-validate the current Kratos session.
|
||||||
* Calls POST /api/auth/refresh/ with the current token.
|
*
|
||||||
* On success, saves the new token to DataManager and TokenStorage.
|
* Identity is owned by Ory Kratos. Native Kratos session tokens are
|
||||||
* On failure, returns an error (caller decides whether to trigger logout).
|
* long-lived and there is no native refresh endpoint — "refresh" here
|
||||||
|
* means: ask Kratos whether the session is still active (`/sessions/whoami`).
|
||||||
|
*
|
||||||
|
* - Session still valid → returns the same (unchanged) token.
|
||||||
|
* - Session gone → returns an error; the caller should sign out.
|
||||||
|
*
|
||||||
|
* The method name is kept so the Coil image interceptor and the
|
||||||
|
* `ApiClient` 401 plumbing continue to compile.
|
||||||
*/
|
*/
|
||||||
suspend fun refreshToken(): ApiResult<String> {
|
suspend fun refreshToken(): ApiResult<String> {
|
||||||
val currentToken = getToken() ?: return ApiResult.Error("No token", 401)
|
val currentToken = getToken() ?: return ApiResult.Error(ClientStrings.t("err.api.no_token"), 401)
|
||||||
val result = authApi.refreshToken(currentToken)
|
return when (val result = authApi.refreshToken(currentToken)) {
|
||||||
if (result is ApiResult.Success) {
|
// Kratos session tokens are never rotated — echo the same token
|
||||||
DataManager.setAuthToken(result.data.token)
|
// back when the session is confirmed still valid.
|
||||||
com.tt.honeyDue.storage.TokenStorage.saveToken(result.data.token)
|
is ApiResult.Success -> ApiResult.Success(currentToken)
|
||||||
}
|
|
||||||
return when (result) {
|
|
||||||
is ApiResult.Success -> ApiResult.Success(result.data.token)
|
|
||||||
is ApiResult.Error -> ApiResult.Error(result.message, result.code)
|
is ApiResult.Error -> ApiResult.Error(result.message, result.code)
|
||||||
else -> ApiResult.Error("Unexpected state")
|
else -> ApiResult.Error(ClientStrings.t("err.api.unexpected_state"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1305,7 +1311,7 @@ object APILayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fetch from API
|
// Fetch from API
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = authApi.getCurrentUser(token)
|
val result = authApi.getCurrentUser(token)
|
||||||
|
|
||||||
// Update DataManager on success
|
// Update DataManager on success
|
||||||
@@ -1316,6 +1322,15 @@ object APILayer {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a client-owned email-verification flow and send the code.
|
||||||
|
* Call from the verification screen on appear and on "resend". See
|
||||||
|
* [AuthApi.startEmailVerification] for why the screen must own the flow.
|
||||||
|
*/
|
||||||
|
suspend fun startEmailVerification(email: String): ApiResult<Unit> {
|
||||||
|
return authApi.startEmailVerification(email)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun verifyEmail(token: String, request: VerifyEmailRequest): ApiResult<VerifyEmailResponse> {
|
suspend fun verifyEmail(token: String, request: VerifyEmailRequest): ApiResult<VerifyEmailResponse> {
|
||||||
return authApi.verifyEmail(token, request)
|
return authApi.verifyEmail(token, request)
|
||||||
}
|
}
|
||||||
@@ -1376,7 +1391,7 @@ object APILayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteAccount(password: String? = null, confirmation: String? = null): ApiResult<Unit> {
|
suspend fun deleteAccount(password: String? = null, confirmation: String? = null): ApiResult<Unit> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = authApi.deleteAccount(token, DeleteAccountRequest(password = password, confirmation = confirmation))
|
val result = authApi.deleteAccount(token, DeleteAccountRequest(password = password, confirmation = confirmation))
|
||||||
|
|
||||||
// Clear DataManager on successful deletion
|
// Clear DataManager on successful deletion
|
||||||
@@ -1413,7 +1428,7 @@ object APILayer {
|
|||||||
bytes: ByteArray,
|
bytes: ByteArray,
|
||||||
fileName: String,
|
fileName: String,
|
||||||
): ApiResult<Int> {
|
): ApiResult<Int> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
return uploadApi.uploadOne(
|
return uploadApi.uploadOne(
|
||||||
token = token,
|
token = token,
|
||||||
category = category,
|
category = category,
|
||||||
@@ -1426,46 +1441,46 @@ object APILayer {
|
|||||||
// ==================== Notification Operations ====================
|
// ==================== Notification Operations ====================
|
||||||
|
|
||||||
suspend fun registerDevice(request: DeviceRegistrationRequest): ApiResult<DeviceRegistrationResponse> {
|
suspend fun registerDevice(request: DeviceRegistrationRequest): ApiResult<DeviceRegistrationResponse> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
return notificationApi.registerDevice(token, request)
|
return notificationApi.registerDevice(token, request)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun unregisterDevice(deviceId: Int): ApiResult<Unit> {
|
suspend fun unregisterDevice(deviceId: Int): ApiResult<Unit> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
return notificationApi.unregisterDevice(token, deviceId)
|
return notificationApi.unregisterDevice(token, deviceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getNotificationPreferences(): ApiResult<NotificationPreference> {
|
suspend fun getNotificationPreferences(): ApiResult<NotificationPreference> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = notificationApi.getNotificationPreferences(token)
|
val result = notificationApi.getNotificationPreferences(token)
|
||||||
if (result is ApiResult.Success) DataManager.setNotificationPreferences(result.data)
|
if (result is ApiResult.Success) DataManager.setNotificationPreferences(result.data)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateNotificationPreferences(request: UpdateNotificationPreferencesRequest): ApiResult<NotificationPreference> {
|
suspend fun updateNotificationPreferences(request: UpdateNotificationPreferencesRequest): ApiResult<NotificationPreference> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = notificationApi.updateNotificationPreferences(token, request)
|
val result = notificationApi.updateNotificationPreferences(token, request)
|
||||||
if (result is ApiResult.Success) DataManager.setNotificationPreferences(result.data)
|
if (result is ApiResult.Success) DataManager.setNotificationPreferences(result.data)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getNotificationHistory(): ApiResult<List<Notification>> {
|
suspend fun getNotificationHistory(): ApiResult<List<Notification>> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
return notificationApi.getNotificationHistory(token)
|
return notificationApi.getNotificationHistory(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun markNotificationAsRead(notificationId: Int): ApiResult<MessageResponse> {
|
suspend fun markNotificationAsRead(notificationId: Int): ApiResult<MessageResponse> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
return notificationApi.markNotificationAsRead(token, notificationId)
|
return notificationApi.markNotificationAsRead(token, notificationId)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun markAllNotificationsAsRead(): ApiResult<MessageResponse> {
|
suspend fun markAllNotificationsAsRead(): ApiResult<MessageResponse> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
return notificationApi.markAllNotificationsAsRead(token)
|
return notificationApi.markAllNotificationsAsRead(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getUnreadCount(): ApiResult<UnreadCountResponse> {
|
suspend fun getUnreadCount(): ApiResult<UnreadCountResponse> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
return notificationApi.getUnreadCount(token)
|
return notificationApi.getUnreadCount(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1476,7 +1491,7 @@ object APILayer {
|
|||||||
* Returns cached data from DataManager if available and forceRefresh is false.
|
* Returns cached data from DataManager if available and forceRefresh is false.
|
||||||
*/
|
*/
|
||||||
suspend fun getSubscriptionStatus(forceRefresh: Boolean = false): ApiResult<SubscriptionStatus> {
|
suspend fun getSubscriptionStatus(forceRefresh: Boolean = false): ApiResult<SubscriptionStatus> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
|
|
||||||
// Return cached subscription if available and not forcing refresh
|
// Return cached subscription if available and not forcing refresh
|
||||||
if (!forceRefresh) {
|
if (!forceRefresh) {
|
||||||
@@ -1500,7 +1515,7 @@ object APILayer {
|
|||||||
* Verify Android purchase with backend
|
* Verify Android purchase with backend
|
||||||
*/
|
*/
|
||||||
suspend fun verifyAndroidPurchase(purchaseToken: String, productId: String): ApiResult<VerificationResponse> {
|
suspend fun verifyAndroidPurchase(purchaseToken: String, productId: String): ApiResult<VerificationResponse> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
return subscriptionApi.verifyAndroidPurchase(token, purchaseToken, productId)
|
return subscriptionApi.verifyAndroidPurchase(token, purchaseToken, productId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1508,7 +1523,7 @@ object APILayer {
|
|||||||
* Verify iOS receipt with backend
|
* Verify iOS receipt with backend
|
||||||
*/
|
*/
|
||||||
suspend fun verifyIOSReceipt(receiptData: String, transactionId: String): ApiResult<VerificationResponse> {
|
suspend fun verifyIOSReceipt(receiptData: String, transactionId: String): ApiResult<VerificationResponse> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
return subscriptionApi.verifyIOSReceipt(token, receiptData, transactionId)
|
return subscriptionApi.verifyIOSReceipt(token, receiptData, transactionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1516,7 +1531,7 @@ object APILayer {
|
|||||||
* Fetch feature benefits from backend (requires auth).
|
* Fetch feature benefits from backend (requires auth).
|
||||||
*/
|
*/
|
||||||
suspend fun getFeatureBenefits(): ApiResult<List<FeatureBenefit>> {
|
suspend fun getFeatureBenefits(): ApiResult<List<FeatureBenefit>> {
|
||||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
val token = getToken() ?: return ApiResult.Error(ClientStrings.t("err.not_authenticated"), 401)
|
||||||
val result = subscriptionApi.getFeatureBenefits(token)
|
val result = subscriptionApi.getFeatureBenefits(token)
|
||||||
if (result is ApiResult.Success) {
|
if (result is ApiResult.Success) {
|
||||||
DataManager.setFeatureBenefits(result.data)
|
DataManager.setFeatureBenefits(result.data)
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package com.tt.honeyDue.network
|
package com.tt.honeyDue.network
|
||||||
|
|
||||||
import com.tt.honeyDue.data.DataManager
|
import com.tt.honeyDue.data.DataManager
|
||||||
import com.tt.honeyDue.models.TokenRefreshResponse
|
|
||||||
import com.tt.honeyDue.storage.TokenStorage
|
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.call.*
|
import io.ktor.client.call.*
|
||||||
import io.ktor.client.plugins.*
|
import io.ktor.client.plugins.*
|
||||||
@@ -34,6 +32,30 @@ expect fun getDeviceLanguage(): String
|
|||||||
*/
|
*/
|
||||||
expect fun getDeviceTimezone(): String
|
expect fun getDeviceTimezone(): String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The HTTP header the honeyDue API expects on authenticated requests.
|
||||||
|
*
|
||||||
|
* Identity is owned by Ory Kratos; the honeyDue API now authenticates a
|
||||||
|
* request by validating the Kratos **session token** carried on this header.
|
||||||
|
* This replaces the old `Authorization: Token <token>` scheme.
|
||||||
|
*
|
||||||
|
* Every honeyDue `*Api.kt` client sends this header via [authHeader]; image
|
||||||
|
* loading uses it through [CoilAuthInterceptor].
|
||||||
|
*/
|
||||||
|
const val SESSION_TOKEN_HEADER: String = "X-Session-Token"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the honeyDue session-token header on a request.
|
||||||
|
*
|
||||||
|
* Usage in an `*Api.kt` client:
|
||||||
|
* ```kotlin
|
||||||
|
* client.get("$baseUrl/tasks/") { authHeader(token) }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
fun HttpRequestBuilder.authHeader(token: String) {
|
||||||
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mutex to prevent multiple concurrent token refresh attempts.
|
* Mutex to prevent multiple concurrent token refresh attempts.
|
||||||
* When one request triggers a 401, only one refresh call is made;
|
* When one request triggers a 401, only one refresh call is made;
|
||||||
@@ -80,37 +102,30 @@ fun HttpClientConfig<*>.installCommonPlugins() {
|
|||||||
socketTimeoutMillis = 30_000 // 30 seconds
|
socketTimeoutMillis = 30_000 // 30 seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
// Task 3: Token refresh on 401 responses
|
// Task 3: Kratos session validation on 401 responses.
|
||||||
|
//
|
||||||
|
// The honeyDue API now authenticates via the Kratos session token on the
|
||||||
|
// X-Session-Token header. A 401 from the API means that token was
|
||||||
|
// rejected. We confirm with Kratos whether the session is genuinely gone:
|
||||||
|
// - still valid -> throw TokenExpiredException(refreshed = true) (retry)
|
||||||
|
// - gone -> clear auth, throw TokenExpiredException(false) (re-login)
|
||||||
HttpResponseValidator {
|
HttpResponseValidator {
|
||||||
validateResponse { response ->
|
validateResponse { response ->
|
||||||
if (response.status.value == 401) {
|
if (response.status.value == 401) {
|
||||||
// Check if this is a token_expired error (not invalid credentials)
|
|
||||||
val bodyText = response.bodyAsText()
|
|
||||||
val isTokenExpired = bodyText.contains("token_expired") ||
|
|
||||||
bodyText.contains("Token has expired") ||
|
|
||||||
bodyText.contains("expired")
|
|
||||||
|
|
||||||
if (isTokenExpired) {
|
|
||||||
val currentToken = DataManager.authToken.value
|
val currentToken = DataManager.authToken.value
|
||||||
if (currentToken != null) {
|
if (currentToken != null) {
|
||||||
// Use mutex to prevent concurrent refresh attempts
|
// Use the mutex so concurrent 401s only trigger one
|
||||||
val refreshed = tokenRefreshMutex.withLock {
|
// whoami check against Kratos.
|
||||||
// Double-check: another coroutine may have already refreshed
|
val stillValid = tokenRefreshMutex.withLock {
|
||||||
val tokenAfterLock = DataManager.authToken.value
|
|
||||||
if (tokenAfterLock != currentToken) {
|
|
||||||
// Token was already refreshed by another coroutine
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
attemptTokenRefresh(currentToken)
|
attemptTokenRefresh(currentToken)
|
||||||
}
|
}
|
||||||
}
|
if (!stillValid) {
|
||||||
if (!refreshed) {
|
// Session is gone — clear auth and route to login.
|
||||||
// Refresh failed — clear auth and trigger logout
|
|
||||||
DataManager.clear()
|
DataManager.clear()
|
||||||
}
|
}
|
||||||
// Throw so the caller can retry (or handle the logout)
|
// Throw so the caller can retry (still valid) or handle
|
||||||
throw TokenExpiredException(refreshed)
|
// the forced logout (session gone).
|
||||||
}
|
throw TokenExpiredException(stillValid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,13 +133,25 @@ fun HttpClientConfig<*>.installCommonPlugins() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to refresh the auth token by calling POST /api/auth/refresh/.
|
* Re-validate the current Kratos session.
|
||||||
* Returns true if refresh succeeded and the new token was saved.
|
*
|
||||||
|
* Identity is owned by Ory Kratos. Native Kratos session tokens are
|
||||||
|
* long-lived and there is **no native refresh endpoint** — when a session
|
||||||
|
* genuinely expires the user must sign in again. So "refresh" here means:
|
||||||
|
* ask Kratos `GET /sessions/whoami` whether the session is still active.
|
||||||
|
*
|
||||||
|
* - returns `true` → the session is still valid; the original 401 was
|
||||||
|
* transient (e.g. a brief replication lag) and the caller may retry.
|
||||||
|
* - returns `false` → the session is gone; the caller should clear auth and
|
||||||
|
* route the user back to login.
|
||||||
|
*
|
||||||
|
* The token itself is never rotated — [DataManager]/[TokenStorage] keep the
|
||||||
|
* same value either way.
|
||||||
*/
|
*/
|
||||||
private suspend fun attemptTokenRefresh(currentToken: String): Boolean {
|
private suspend fun attemptTokenRefresh(currentToken: String): Boolean {
|
||||||
return try {
|
return try {
|
||||||
// Use a minimal client to avoid recursive interceptor triggers
|
// Use a minimal client to avoid recursive interceptor triggers.
|
||||||
val refreshClient = HttpClient {
|
val whoamiClient = HttpClient {
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
json(Json {
|
json(Json {
|
||||||
ignoreUnknownKeys = true
|
ignoreUnknownKeys = true
|
||||||
@@ -137,38 +164,40 @@ private suspend fun attemptTokenRefresh(currentToken: String): Boolean {
|
|||||||
socketTimeoutMillis = 15_000
|
socketTimeoutMillis = 15_000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val baseUrl = ApiConfig.getBaseUrl()
|
val kratosUrl = ApiConfig.getKratosBaseUrl()
|
||||||
val response = refreshClient.post("$baseUrl/auth/refresh/") {
|
val response = whoamiClient.get("$kratosUrl/sessions/whoami") {
|
||||||
header("Authorization", "Token $currentToken")
|
header(SESSION_TOKEN_HEADER, currentToken)
|
||||||
contentType(ContentType.Application.Json)
|
accept(ContentType.Application.Json)
|
||||||
}
|
}
|
||||||
refreshClient.close()
|
whoamiClient.close()
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
val tokenResponse = response.body<TokenRefreshResponse>()
|
// Session still valid — keep the same token.
|
||||||
// Save the new token to both DataManager and persistent storage
|
println("[ApiClient] Kratos session still valid")
|
||||||
DataManager.setAuthToken(tokenResponse.token)
|
|
||||||
TokenStorage.saveToken(tokenResponse.token)
|
|
||||||
println("[ApiClient] Token refreshed successfully")
|
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
println("[ApiClient] Token refresh failed: ${response.status.value}")
|
println("[ApiClient] Kratos session invalid: ${response.status.value}")
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
println("[ApiClient] Token refresh error: ${e.message}")
|
println("[ApiClient] Kratos session check error: ${e.message}")
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exception thrown when a 401 response indicates an expired token.
|
* Exception thrown when a 401 response indicates an expired/invalid session.
|
||||||
* [refreshed] indicates whether the token was successfully refreshed.
|
*
|
||||||
* Callers can catch this and retry the request if refreshed is true.
|
* [refreshed] indicates whether the Kratos session was re-validated and is
|
||||||
|
* still usable. Callers can catch this and retry the request when `refreshed`
|
||||||
|
* is true; when false the user must re-authenticate.
|
||||||
|
*
|
||||||
|
* The name is retained from the pre-Kratos token scheme so existing callers
|
||||||
|
* ([CoilAuthInterceptor] plumbing, tests) continue to compile.
|
||||||
*/
|
*/
|
||||||
class TokenExpiredException(val refreshed: Boolean) : Exception(
|
class TokenExpiredException(val refreshed: Boolean) : Exception(
|
||||||
if (refreshed) "Token was expired but has been refreshed — retry the request"
|
if (refreshed) "Session was briefly rejected but is still valid — retry the request" // i18n-ignore: internal log/diagnostic message, non-UI
|
||||||
else "Token expired and refresh failed — user must re-authenticate"
|
else "Session expired — user must re-authenticate" // i18n-ignore: internal log/diagnostic message, non-UI
|
||||||
)
|
)
|
||||||
|
|
||||||
object ApiClient {
|
object ApiClient {
|
||||||
@@ -185,6 +214,13 @@ object ApiClient {
|
|||||||
*/
|
*/
|
||||||
fun getMediaBaseUrl(): String = ApiConfig.getMediaBaseUrl()
|
fun getMediaBaseUrl(): String = ApiConfig.getMediaBaseUrl()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Ory Kratos public API base URL. Identity flows (login,
|
||||||
|
* registration, recovery, verification, OIDC sign-in) run against this
|
||||||
|
* host — NOT [getBaseUrl].
|
||||||
|
*/
|
||||||
|
fun getKratosBaseUrl(): String = ApiConfig.getKratosBaseUrl()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Print current environment configuration
|
* Print current environment configuration
|
||||||
*/
|
*/
|
||||||
@@ -193,5 +229,6 @@ object ApiClient {
|
|||||||
println("Environment: ${ApiConfig.getEnvironmentName()}")
|
println("Environment: ${ApiConfig.getEnvironmentName()}")
|
||||||
println("Base URL: ${getBaseUrl()}")
|
println("Base URL: ${getBaseUrl()}")
|
||||||
println("Media URL: ${getMediaBaseUrl()}")
|
println("Media URL: ${getMediaBaseUrl()}")
|
||||||
|
println("Kratos URL: ${getKratosBaseUrl()}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ package com.tt.honeyDue.network
|
|||||||
*/
|
*/
|
||||||
object ApiConfig {
|
object ApiConfig {
|
||||||
// ⚠️ CHANGE THIS TO TOGGLE ENVIRONMENT ⚠️
|
// ⚠️ CHANGE THIS TO TOGGLE ENVIRONMENT ⚠️
|
||||||
val CURRENT_ENV = Environment.LOCAL
|
val CURRENT_ENV = Environment.PROD
|
||||||
|
|
||||||
enum class Environment {
|
enum class Environment {
|
||||||
LOCAL,
|
LOCAL,
|
||||||
@@ -40,14 +40,36 @@ object ApiConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Ory Kratos public API base URL.
|
||||||
|
*
|
||||||
|
* Identity (login, registration, recovery, verification, OIDC sign-in) is
|
||||||
|
* owned by Ory Kratos — NOT the honeyDue Go API. The native (`api`)
|
||||||
|
* self-service flows live under `{kratosBaseUrl}/self-service/...`.
|
||||||
|
*
|
||||||
|
* - LOCAL: a Kratos instance running on the dev machine (default public
|
||||||
|
* port `4433`). The Android emulator reaches the host via `10.0.2.2`,
|
||||||
|
* the iOS simulator via `127.0.0.1` — both resolved by [getLocalhostAddress].
|
||||||
|
* - DEV / PROD: the hosted Kratos at `auth.myhoneydue.com`.
|
||||||
|
*
|
||||||
|
* No trailing slash — callers append `/self-service/...`.
|
||||||
|
*/
|
||||||
|
fun getKratosBaseUrl(): String {
|
||||||
|
return when (CURRENT_ENV) {
|
||||||
|
Environment.LOCAL -> "http://${getLocalhostAddress()}:4433"
|
||||||
|
Environment.DEV -> "https://auth.myhoneydue.com"
|
||||||
|
Environment.PROD -> "https://auth.myhoneydue.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get environment name for logging
|
* Get environment name for logging
|
||||||
*/
|
*/
|
||||||
fun getEnvironmentName(): String {
|
fun getEnvironmentName(): String {
|
||||||
return when (CURRENT_ENV) {
|
return when (CURRENT_ENV) {
|
||||||
Environment.LOCAL -> "Local (${getLocalhostAddress()}:8000)"
|
Environment.LOCAL -> "Local (${getLocalhostAddress()}:8000)" // i18n-ignore: dev environment label, non-UI debug menu
|
||||||
Environment.DEV -> "Dev Server (devapi.myhoneydue.com)"
|
Environment.DEV -> "Dev Server (devapi.myhoneydue.com)" // i18n-ignore: dev environment label, non-UI debug menu
|
||||||
Environment.PROD -> "Production (api.myhoneydue.com)"
|
Environment.PROD -> "Production (api.myhoneydue.com)" // i18n-ignore: dev environment label, non-UI debug menu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -7,14 +7,18 @@ import coil3.request.ErrorResult
|
|||||||
import coil3.request.ImageResult
|
import coil3.request.ImageResult
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Coil3 [Interceptor] that attaches an `Authorization` header to every
|
* Coil3 [Interceptor] that attaches the honeyDue session-token header to every
|
||||||
* outgoing image request and, on an HTTP 401 response, refreshes the token
|
* outgoing image request and, on an HTTP 401 response, re-validates the
|
||||||
* and retries exactly once.
|
* session and retries exactly once.
|
||||||
|
*
|
||||||
|
* honeyDue's identity is owned by Ory Kratos. Authenticated honeyDue API
|
||||||
|
* requests — including authenticated media — carry the Kratos session token
|
||||||
|
* on the **`X-Session-Token`** header (the old `Authorization: Token …` scheme
|
||||||
|
* is gone). This interceptor centralises that concern so individual
|
||||||
|
* composables don't thread the token through themselves.
|
||||||
*
|
*
|
||||||
* Mirrors the behavior of the iOS `AuthenticatedImage` in
|
* Mirrors the behavior of the iOS `AuthenticatedImage` in
|
||||||
* `iosApp/iosApp/Components/AuthenticatedImage.swift`, centralising the
|
* `iosApp/iosApp/Components/AuthenticatedImage.swift`.
|
||||||
* concern so individual composables don't need to thread the token through
|
|
||||||
* themselves.
|
|
||||||
*
|
*
|
||||||
* Usage — install on the singleton [coil3.ImageLoader]:
|
* Usage — install on the singleton [coil3.ImageLoader]:
|
||||||
* ```kotlin
|
* ```kotlin
|
||||||
@@ -23,24 +27,28 @@ import coil3.request.ImageResult
|
|||||||
* add(CoilAuthInterceptor(
|
* add(CoilAuthInterceptor(
|
||||||
* tokenProvider = { TokenStorage.getToken() },
|
* tokenProvider = { TokenStorage.getToken() },
|
||||||
* refreshToken = { (APILayer.refreshToken() as? ApiResult.Success)?.data },
|
* refreshToken = { (APILayer.refreshToken() as? ApiResult.Success)?.data },
|
||||||
* authScheme = "Token",
|
|
||||||
* ))
|
* ))
|
||||||
* add(KtorNetworkFetcherFactory())
|
* add(KtorNetworkFetcherFactory())
|
||||||
* }
|
* }
|
||||||
* .build()
|
* .build()
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @param tokenProvider Suspending supplier of the current auth token. Returning
|
* @param tokenProvider Suspending supplier of the current session token.
|
||||||
* `null` means "no token available" — the request proceeds unauthenticated.
|
* Returning `null` means "no token available" — the request proceeds
|
||||||
* @param refreshToken Suspending supplier that refreshes the backing session and
|
* unauthenticated so anonymous endpoints still work.
|
||||||
* returns a fresh token, or `null` if refresh failed.
|
* @param refreshToken Suspending supplier that re-validates the session and
|
||||||
* @param authScheme The auth scheme to prefix the token with (default `Token`
|
* returns a still-valid token, or `null` if the session is gone. With
|
||||||
* to match the existing Go backend — use `Bearer` for JWT deployments).
|
* Kratos, session tokens are not rotated — this typically echoes the same
|
||||||
|
* token back when the session is still active.
|
||||||
|
* @param headerName The HTTP header carrying the token. Defaults to
|
||||||
|
* [SESSION_TOKEN_HEADER] (`X-Session-Token`). The token is sent as the bare
|
||||||
|
* header value — there is no `<scheme> ` prefix under the Kratos
|
||||||
|
* session-token scheme.
|
||||||
*/
|
*/
|
||||||
class CoilAuthInterceptor(
|
class CoilAuthInterceptor(
|
||||||
private val tokenProvider: suspend () -> String?,
|
private val tokenProvider: suspend () -> String?,
|
||||||
private val refreshToken: suspend () -> String?,
|
private val refreshToken: suspend () -> String?,
|
||||||
private val authScheme: String = "Token",
|
private val headerName: String = SESSION_TOKEN_HEADER,
|
||||||
) : Interceptor {
|
) : Interceptor {
|
||||||
|
|
||||||
override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
|
override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
|
||||||
@@ -55,26 +63,26 @@ class CoilAuthInterceptor(
|
|||||||
val authed = chain.request.newBuilder()
|
val authed = chain.request.newBuilder()
|
||||||
.httpHeaders(
|
.httpHeaders(
|
||||||
chain.request.httpHeaders.newBuilder()
|
chain.request.httpHeaders.newBuilder()
|
||||||
.set(HEADER_AUTHORIZATION, "$authScheme $token")
|
.set(headerName, token)
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val result = chain.withRequest(authed).proceed()
|
val result = chain.withRequest(authed).proceed()
|
||||||
|
|
||||||
// If the server rejected the token, try refreshing once.
|
// If the server rejected the token, re-validate the session once.
|
||||||
if (result.isUnauthorized()) {
|
if (result.isUnauthorized()) {
|
||||||
val newToken = refreshToken() ?: return result
|
val newToken = refreshToken() ?: return result
|
||||||
val retried = authed.newBuilder()
|
val retried = authed.newBuilder()
|
||||||
.httpHeaders(
|
.httpHeaders(
|
||||||
authed.httpHeaders.newBuilder()
|
authed.httpHeaders.newBuilder()
|
||||||
.set(HEADER_AUTHORIZATION, "$authScheme $newToken")
|
.set(headerName, newToken)
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
// Only retry *once* — whatever comes back from this call is final,
|
// Only retry *once* — whatever comes back from this call is final,
|
||||||
// even if it is itself a 401. This guards against an infinite loop
|
// even if it is itself a 401. This guards against an infinite loop
|
||||||
// when refresh succeeds but the backing account is still revoked.
|
// when the session is still revoked.
|
||||||
return chain.withRequest(retried).proceed()
|
return chain.withRequest(retried).proceed()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +96,6 @@ class CoilAuthInterceptor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val HEADER_AUTHORIZATION = "Authorization"
|
|
||||||
private const val HTTP_UNAUTHORIZED = 401
|
private const val HTTP_UNAUTHORIZED = 401
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.tt.honeyDue.network
|
package com.tt.honeyDue.network
|
||||||
|
|
||||||
|
import com.tt.honeyDue.i18n.ClientStrings
|
||||||
|
|
||||||
import com.tt.honeyDue.models.*
|
import com.tt.honeyDue.models.*
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.call.*
|
import io.ktor.client.call.*
|
||||||
@@ -18,7 +20,7 @@ class ContractorApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
): ApiResult<List<ContractorSummary>> {
|
): ApiResult<List<ContractorSummary>> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/contractors/") {
|
val response = client.get("$baseUrl/contractors/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
specialty?.let { parameter("specialty", it) }
|
specialty?.let { parameter("specialty", it) }
|
||||||
isFavorite?.let { parameter("is_favorite", it) }
|
isFavorite?.let { parameter("is_favorite", it) }
|
||||||
isActive?.let { parameter("is_active", it) }
|
isActive?.let { parameter("is_active", it) }
|
||||||
@@ -28,33 +30,33 @@ class ContractorApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to fetch contractors", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_fetch_contractors"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getContractor(token: String, id: Int): ApiResult<Contractor> {
|
suspend fun getContractor(token: String, id: Int): ApiResult<Contractor> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/contractors/$id/") {
|
val response = client.get("$baseUrl/contractors/$id/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to fetch contractor", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_fetch_contractor"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun createContractor(token: String, request: ContractorCreateRequest): ApiResult<Contractor> {
|
suspend fun createContractor(token: String, request: ContractorCreateRequest): ApiResult<Contractor> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.post("$baseUrl/contractors/") {
|
val response = client.post("$baseUrl/contractors/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
setBody(request)
|
setBody(request)
|
||||||
}
|
}
|
||||||
@@ -65,19 +67,19 @@ class ContractorApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
val errorBody = try {
|
val errorBody = try {
|
||||||
response.body<String>()
|
response.body<String>()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
"Failed to create contractor"
|
ClientStrings.t("err.api.failed_create_contractor")
|
||||||
}
|
}
|
||||||
ApiResult.Error(errorBody, response.status.value)
|
ApiResult.Error(errorBody, response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateContractor(token: String, id: Int, request: ContractorUpdateRequest): ApiResult<Contractor> {
|
suspend fun updateContractor(token: String, id: Int, request: ContractorUpdateRequest): ApiResult<Contractor> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.patch("$baseUrl/contractors/$id/") {
|
val response = client.patch("$baseUrl/contractors/$id/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
setBody(request)
|
setBody(request)
|
||||||
}
|
}
|
||||||
@@ -88,76 +90,76 @@ class ContractorApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
val errorBody = try {
|
val errorBody = try {
|
||||||
response.body<String>()
|
response.body<String>()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
"Failed to update contractor"
|
ClientStrings.t("err.api.failed_update_contractor")
|
||||||
}
|
}
|
||||||
ApiResult.Error(errorBody, response.status.value)
|
ApiResult.Error(errorBody, response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteContractor(token: String, id: Int): ApiResult<Unit> {
|
suspend fun deleteContractor(token: String, id: Int): ApiResult<Unit> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.delete("$baseUrl/contractors/$id/") {
|
val response = client.delete("$baseUrl/contractors/$id/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(Unit)
|
ApiResult.Success(Unit)
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to delete contractor", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_delete_contractor"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun toggleFavorite(token: String, id: Int): ApiResult<Contractor> {
|
suspend fun toggleFavorite(token: String, id: Int): ApiResult<Contractor> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.post("$baseUrl/contractors/$id/toggle-favorite/") {
|
val response = client.post("$baseUrl/contractors/$id/toggle-favorite/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to toggle favorite", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_toggle_favorite"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getContractorTasks(token: String, id: Int): ApiResult<List<TaskResponse>> {
|
suspend fun getContractorTasks(token: String, id: Int): ApiResult<List<TaskResponse>> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/contractors/$id/tasks/") {
|
val response = client.get("$baseUrl/contractors/$id/tasks/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to fetch contractor tasks", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_fetch_contractor_tasks"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getContractorsByResidence(token: String, residenceId: Int): ApiResult<List<ContractorSummary>> {
|
suspend fun getContractorsByResidence(token: String, residenceId: Int): ApiResult<List<ContractorSummary>> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/contractors/by-residence/$residenceId/") {
|
val response = client.get("$baseUrl/contractors/by-residence/$residenceId/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to fetch contractors for residence", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_fetch_contractors_for_residence"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.tt.honeyDue.network
|
package com.tt.honeyDue.network
|
||||||
|
|
||||||
|
import com.tt.honeyDue.i18n.ClientStrings
|
||||||
|
|
||||||
import com.tt.honeyDue.models.*
|
import com.tt.honeyDue.models.*
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.call.*
|
import io.ktor.client.call.*
|
||||||
@@ -26,7 +28,7 @@ class DocumentApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
): ApiResult<List<Document>> {
|
): ApiResult<List<Document>> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/documents/") {
|
val response = client.get("$baseUrl/documents/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
residenceId?.let { parameter("residence", it) }
|
residenceId?.let { parameter("residence", it) }
|
||||||
documentType?.let { parameter("document_type", it) }
|
documentType?.let { parameter("document_type", it) }
|
||||||
isActive?.let { parameter("is_active", it) }
|
isActive?.let { parameter("is_active", it) }
|
||||||
@@ -37,26 +39,26 @@ class DocumentApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to fetch documents", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_fetch_documents"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getDocument(token: String, id: Int): ApiResult<Document> {
|
suspend fun getDocument(token: String, id: Int): ApiResult<Document> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/documents/$id/") {
|
val response = client.get("$baseUrl/documents/$id/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to fetch document", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_fetch_document"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,17 +119,17 @@ class DocumentApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
// Send first file as "file" (backend only accepts single file)
|
// Send first file as "file" (backend only accepts single file)
|
||||||
append("file", fileBytesList[0], Headers.build {
|
append("file", fileBytesList[0], Headers.build {
|
||||||
append(HttpHeaders.ContentType, mimeTypesList.getOrElse(0) { "application/octet-stream" })
|
append(HttpHeaders.ContentType, mimeTypesList.getOrElse(0) { "application/octet-stream" })
|
||||||
append(HttpHeaders.ContentDisposition, "filename=\"${fileNamesList.getOrElse(0) { "file_0" }}\"")
|
append(HttpHeaders.ContentDisposition, "filename=\"${fileNamesList.getOrElse(0) { "file_0" }}\"") // i18n-ignore: Content-Disposition filename, non-UI
|
||||||
})
|
})
|
||||||
} else if (fileBytes != null && fileName != null && mimeType != null) {
|
} else if (fileBytes != null && fileName != null && mimeType != null) {
|
||||||
append("file", fileBytes, Headers.build {
|
append("file", fileBytes, Headers.build {
|
||||||
append(HttpHeaders.ContentType, mimeType)
|
append(HttpHeaders.ContentType, mimeType)
|
||||||
append(HttpHeaders.ContentDisposition, "filename=\"$fileName\"")
|
append(HttpHeaders.ContentDisposition, "filename=\"$fileName\"") // i18n-ignore: Content-Disposition filename, non-UI
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If no file, use JSON
|
// If no file, use JSON
|
||||||
@@ -143,7 +145,7 @@ class DocumentApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
residenceId = residenceId
|
residenceId = residenceId
|
||||||
)
|
)
|
||||||
client.post("$baseUrl/documents/") {
|
client.post("$baseUrl/documents/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
setBody(request)
|
setBody(request)
|
||||||
}
|
}
|
||||||
@@ -155,12 +157,12 @@ class DocumentApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
val errorBody = try {
|
val errorBody = try {
|
||||||
response.body<String>()
|
response.body<String>()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
"Failed to create document"
|
ClientStrings.t("err.api.failed_create_document")
|
||||||
}
|
}
|
||||||
ApiResult.Error(errorBody, response.status.value)
|
ApiResult.Error(errorBody, response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,7 +203,7 @@ class DocumentApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
expiryDate = endDate // Map endDate to expiryDate
|
expiryDate = endDate // Map endDate to expiryDate
|
||||||
)
|
)
|
||||||
val response = client.patch("$baseUrl/documents/$id/") {
|
val response = client.patch("$baseUrl/documents/$id/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
setBody(request)
|
setBody(request)
|
||||||
}
|
}
|
||||||
@@ -212,51 +214,51 @@ class DocumentApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
val errorBody = try {
|
val errorBody = try {
|
||||||
response.body<String>()
|
response.body<String>()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
"Failed to update document"
|
ClientStrings.t("err.api.failed_update_document")
|
||||||
}
|
}
|
||||||
ApiResult.Error(errorBody, response.status.value)
|
ApiResult.Error(errorBody, response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteDocument(token: String, id: Int): ApiResult<Unit> {
|
suspend fun deleteDocument(token: String, id: Int): ApiResult<Unit> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.delete("$baseUrl/documents/$id/") {
|
val response = client.delete("$baseUrl/documents/$id/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(Unit)
|
ApiResult.Success(Unit)
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to delete document", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_delete_document"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun downloadDocument(token: String, url: String): ApiResult<ByteArray> {
|
suspend fun downloadDocument(token: String, url: String): ApiResult<ByteArray> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get(url) {
|
val response = client.get(url) {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to download document", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_download_document"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun activateDocument(token: String, id: Int): ApiResult<Document> {
|
suspend fun activateDocument(token: String, id: Int): ApiResult<Document> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.post("$baseUrl/documents/$id/activate/") {
|
val response = client.post("$baseUrl/documents/$id/activate/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
@@ -264,17 +266,17 @@ class DocumentApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
val document: Document = response.body()
|
val document: Document = response.body()
|
||||||
ApiResult.Success(document)
|
ApiResult.Success(document)
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to activate document", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_activate_document"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deactivateDocument(token: String, id: Int): ApiResult<Document> {
|
suspend fun deactivateDocument(token: String, id: Int): ApiResult<Document> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.post("$baseUrl/documents/$id/deactivate/") {
|
val response = client.post("$baseUrl/documents/$id/deactivate/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
@@ -282,10 +284,10 @@ class DocumentApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
val document: Document = response.body()
|
val document: Document = response.body()
|
||||||
ApiResult.Success(document)
|
ApiResult.Success(document)
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to deactivate document", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_deactivate_document"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,12 +305,12 @@ class DocumentApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
formData = formData {
|
formData = formData {
|
||||||
append("image", imageBytes, Headers.build {
|
append("image", imageBytes, Headers.build {
|
||||||
append(HttpHeaders.ContentType, mimeType)
|
append(HttpHeaders.ContentType, mimeType)
|
||||||
append(HttpHeaders.ContentDisposition, "filename=\"$fileName\"")
|
append(HttpHeaders.ContentDisposition, "filename=\"$fileName\"") // i18n-ignore: Content-Disposition filename, non-UI
|
||||||
})
|
})
|
||||||
caption?.let { append("caption", it) }
|
caption?.let { append("caption", it) }
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
@@ -317,28 +319,28 @@ class DocumentApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
val errorBody = try {
|
val errorBody = try {
|
||||||
response.body<String>()
|
response.body<String>()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
"Failed to upload document image"
|
ClientStrings.t("err.api.failed_upload_document_image")
|
||||||
}
|
}
|
||||||
ApiResult.Error(errorBody, response.status.value)
|
ApiResult.Error(errorBody, response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteDocumentImage(token: String, documentId: Int, imageId: Int): ApiResult<Document> {
|
suspend fun deleteDocumentImage(token: String, documentId: Int, imageId: Int): ApiResult<Document> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.delete("$baseUrl/documents/$documentId/images/$imageId/") {
|
val response = client.delete("$baseUrl/documents/$documentId/images/$imageId/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to delete document image", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_delete_document_image"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.tt.honeyDue.network
|
package com.tt.honeyDue.network
|
||||||
|
|
||||||
|
import com.tt.honeyDue.i18n.ClientStrings
|
||||||
import com.tt.honeyDue.models.ErrorResponse
|
import com.tt.honeyDue.models.ErrorResponse
|
||||||
import io.ktor.client.call.body
|
import io.ktor.client.call.body
|
||||||
import io.ktor.client.statement.HttpResponse
|
import io.ktor.client.statement.HttpResponse
|
||||||
@@ -22,9 +23,9 @@ object ErrorParser {
|
|||||||
if (response.status.value == 429) {
|
if (response.status.value == 429) {
|
||||||
val retryAfter = response.headers["Retry-After"]?.toLongOrNull()
|
val retryAfter = response.headers["Retry-After"]?.toLongOrNull()
|
||||||
return if (retryAfter != null) {
|
return if (retryAfter != null) {
|
||||||
"Too many requests. Please try again in $retryAfter seconds."
|
ClientStrings.t("err.too_many_requests_retry", retryAfter)
|
||||||
} else {
|
} else {
|
||||||
"Too many requests. Please try again later."
|
ClientStrings.t("err.too_many_requests")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,9 +48,9 @@ object ErrorParser {
|
|||||||
// Add field-specific errors if present
|
// Add field-specific errors if present
|
||||||
errorResponse.errors?.let { fieldErrors ->
|
errorResponse.errors?.let { fieldErrors ->
|
||||||
if (fieldErrors.isNotEmpty()) {
|
if (fieldErrors.isNotEmpty()) {
|
||||||
message.append("\n\nDetails:")
|
message.append("\n\n${ClientStrings.t("err.details")}") // i18n-ignore: interpolation residue; 'Details:' localized via ClientStrings
|
||||||
fieldErrors.forEach { (field, errors) ->
|
fieldErrors.forEach { (field, errors) ->
|
||||||
message.append("\n• $field: ${errors.joinToString(", ")}")
|
message.append("\n• $field: ${errors.joinToString(", ")}") // i18n-ignore: interpolation residue; backend field name, non-UI prose
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,13 +61,13 @@ object ErrorParser {
|
|||||||
try {
|
try {
|
||||||
val simpleError = response.body<Map<String, String>>()
|
val simpleError = response.body<Map<String, String>>()
|
||||||
simpleError["error"] ?: simpleError["message"] ?: simpleError["detail"]
|
simpleError["error"] ?: simpleError["message"] ?: simpleError["detail"]
|
||||||
?: "An error occurred (${response.status.value})"
|
?: ClientStrings.t("err.with_status", response.status.value)
|
||||||
} catch (e2: Exception) {
|
} catch (e2: Exception) {
|
||||||
// Last resort: read as plain text
|
// Last resort: read as plain text
|
||||||
try {
|
try {
|
||||||
response.body<String>()
|
response.body<String>()
|
||||||
} catch (e3: Exception) {
|
} catch (e3: Exception) {
|
||||||
"An error occurred (${response.status.value})"
|
ClientStrings.t("err.with_status", response.status.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.tt.honeyDue.network
|
package com.tt.honeyDue.network
|
||||||
|
|
||||||
|
import com.tt.honeyDue.i18n.ClientStrings
|
||||||
|
|
||||||
import com.tt.honeyDue.models.*
|
import com.tt.honeyDue.models.*
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.call.*
|
import io.ktor.client.call.*
|
||||||
@@ -36,80 +38,80 @@ class LookupsApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
suspend fun getResidenceTypes(token: String): ApiResult<List<ResidenceType>> {
|
suspend fun getResidenceTypes(token: String): ApiResult<List<ResidenceType>> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/residences/types/") {
|
val response = client.get("$baseUrl/residences/types/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to fetch residence types", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_fetch_residence_types"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getTaskFrequencies(token: String): ApiResult<List<TaskFrequency>> {
|
suspend fun getTaskFrequencies(token: String): ApiResult<List<TaskFrequency>> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/tasks/frequencies/") {
|
val response = client.get("$baseUrl/tasks/frequencies/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to fetch task frequencies", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_fetch_task_frequencies"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getTaskPriorities(token: String): ApiResult<List<TaskPriority>> {
|
suspend fun getTaskPriorities(token: String): ApiResult<List<TaskPriority>> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/tasks/priorities/") {
|
val response = client.get("$baseUrl/tasks/priorities/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to fetch task priorities", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_fetch_task_priorities"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getTaskCategories(token: String): ApiResult<List<TaskCategory>> {
|
suspend fun getTaskCategories(token: String): ApiResult<List<TaskCategory>> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/tasks/categories/") {
|
val response = client.get("$baseUrl/tasks/categories/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to fetch task categories", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_fetch_task_categories"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getContractorSpecialties(token: String): ApiResult<List<ContractorSpecialty>> {
|
suspend fun getContractorSpecialties(token: String): ApiResult<List<ContractorSpecialty>> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/contractors/specialties/") {
|
val response = client.get("$baseUrl/contractors/specialties/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to fetch contractor specialties", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_fetch_contractor_specialties"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,16 +119,16 @@ class LookupsApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/static_data/") {
|
val response = client.get("$baseUrl/static_data/") {
|
||||||
// Token is optional - endpoint is public
|
// Token is optional - endpoint is public
|
||||||
token?.let { header("Authorization", "Token $it") }
|
token?.let { header(SESSION_TOKEN_HEADER, it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to fetch static data", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_fetch_static_data"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +147,7 @@ class LookupsApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
return try {
|
return try {
|
||||||
val response: HttpResponse = client.get("$baseUrl/static_data/") {
|
val response: HttpResponse = client.get("$baseUrl/static_data/") {
|
||||||
// Token is optional - endpoint is public
|
// Token is optional - endpoint is public
|
||||||
token?.let { header("Authorization", "Token $it") }
|
token?.let { header(SESSION_TOKEN_HEADER, it) }
|
||||||
// Send If-None-Match header for conditional request
|
// Send If-None-Match header for conditional request
|
||||||
currentETag?.let { header("If-None-Match", it) }
|
currentETag?.let { header("If-None-Match", it) }
|
||||||
}
|
}
|
||||||
@@ -158,18 +160,18 @@ class LookupsApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
response.status.isSuccess() -> {
|
response.status.isSuccess() -> {
|
||||||
// Data has changed or first request - get new data and ETag
|
// Data has changed or first request - get new data and ETag
|
||||||
val data: SeededDataResponse = response.body()
|
val data: SeededDataResponse = response.body()
|
||||||
val newETag = response.headers["ETag"]
|
val newETag = response.headers["ETag"] // i18n-ignore: HTTP header name, non-UI
|
||||||
ConditionalResult.Success(data, newETag)
|
ConditionalResult.Success(data, newETag)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
ConditionalResult.Error(
|
ConditionalResult.Error(
|
||||||
"Failed to fetch seeded data",
|
ClientStrings.t("err.api.failed_fetch_seeded_data"),
|
||||||
response.status.value
|
response.status.value
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ConditionalResult.Error(e.message ?: "Unknown error occurred")
|
ConditionalResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.tt.honeyDue.network
|
package com.tt.honeyDue.network
|
||||||
|
|
||||||
|
import com.tt.honeyDue.i18n.ClientStrings
|
||||||
|
|
||||||
import com.tt.honeyDue.models.*
|
import com.tt.honeyDue.models.*
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.call.*
|
import io.ktor.client.call.*
|
||||||
@@ -18,7 +20,7 @@ class NotificationApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
): ApiResult<DeviceRegistrationResponse> {
|
): ApiResult<DeviceRegistrationResponse> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.post("$baseUrl/notifications/devices/register/") {
|
val response = client.post("$baseUrl/notifications/devices/register/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
setBody(request)
|
setBody(request)
|
||||||
}
|
}
|
||||||
@@ -29,12 +31,12 @@ class NotificationApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
val errorBody = try {
|
val errorBody = try {
|
||||||
response.body<Map<String, String>>()
|
response.body<Map<String, String>>()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
mapOf("error" to "Device registration failed")
|
mapOf("error" to ClientStrings.t("err.api.device_registration_failed"))
|
||||||
}
|
}
|
||||||
ApiResult.Error(errorBody["error"] ?: "Device registration failed", response.status.value)
|
ApiResult.Error(errorBody["error"] ?: ClientStrings.t("err.api.device_registration_failed"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,16 +46,16 @@ class NotificationApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
): ApiResult<Unit> {
|
): ApiResult<Unit> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.delete("$baseUrl/notifications/devices/$deviceId/") {
|
val response = client.delete("$baseUrl/notifications/devices/$deviceId/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(Unit)
|
ApiResult.Success(Unit)
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Device unregistration failed", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.device_unregistration_failed"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,16 +65,16 @@ class NotificationApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
suspend fun getNotificationPreferences(token: String): ApiResult<NotificationPreference> {
|
suspend fun getNotificationPreferences(token: String): ApiResult<NotificationPreference> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/notifications/preferences/") {
|
val response = client.get("$baseUrl/notifications/preferences/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to get preferences", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_get_preferences"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +87,7 @@ class NotificationApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
): ApiResult<NotificationPreference> {
|
): ApiResult<NotificationPreference> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.put("$baseUrl/notifications/preferences/") {
|
val response = client.put("$baseUrl/notifications/preferences/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
setBody(request)
|
setBody(request)
|
||||||
}
|
}
|
||||||
@@ -93,27 +95,27 @@ class NotificationApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to update preferences", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_update_preferences"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getNotificationHistory(token: String): ApiResult<List<Notification>> {
|
suspend fun getNotificationHistory(token: String): ApiResult<List<Notification>> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/notifications/") {
|
val response = client.get("$baseUrl/notifications/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
val listResponse: NotificationListResponse = response.body()
|
val listResponse: NotificationListResponse = response.body()
|
||||||
ApiResult.Success(listResponse.results)
|
ApiResult.Success(listResponse.results)
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to get notification history", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_get_notification_history"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,48 +125,48 @@ class NotificationApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
): ApiResult<MessageResponse> {
|
): ApiResult<MessageResponse> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.post("$baseUrl/notifications/$notificationId/read/") {
|
val response = client.post("$baseUrl/notifications/$notificationId/read/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to mark notification as read", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_mark_notification_read"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun markAllNotificationsAsRead(token: String): ApiResult<MessageResponse> {
|
suspend fun markAllNotificationsAsRead(token: String): ApiResult<MessageResponse> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.post("$baseUrl/notifications/mark-all-read/") {
|
val response = client.post("$baseUrl/notifications/mark-all-read/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to mark all notifications as read", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_mark_all_notifications_read"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getUnreadCount(token: String): ApiResult<UnreadCountResponse> {
|
suspend fun getUnreadCount(token: String): ApiResult<UnreadCountResponse> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/notifications/unread-count/") {
|
val response = client.get("$baseUrl/notifications/unread-count/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to get unread count", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_get_unread_count"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.tt.honeyDue.network
|
package com.tt.honeyDue.network
|
||||||
|
|
||||||
|
import com.tt.honeyDue.i18n.ClientStrings
|
||||||
|
|
||||||
import com.tt.honeyDue.models.*
|
import com.tt.honeyDue.models.*
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.call.*
|
import io.ktor.client.call.*
|
||||||
@@ -12,39 +14,39 @@ class ResidenceApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
suspend fun getResidences(token: String): ApiResult<List<ResidenceResponse>> {
|
suspend fun getResidences(token: String): ApiResult<List<ResidenceResponse>> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/residences/") {
|
val response = client.get("$baseUrl/residences/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to fetch residences", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_fetch_residences"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getResidence(token: String, id: Int): ApiResult<ResidenceResponse> {
|
suspend fun getResidence(token: String, id: Int): ApiResult<ResidenceResponse> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/residences/$id/") {
|
val response = client.get("$baseUrl/residences/$id/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to fetch residence", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_fetch_residence"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun createResidence(token: String, request: ResidenceCreateRequest): ApiResult<WithSummaryResponse<ResidenceResponse>> {
|
suspend fun createResidence(token: String, request: ResidenceCreateRequest): ApiResult<WithSummaryResponse<ResidenceResponse>> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.post("$baseUrl/residences/") {
|
val response = client.post("$baseUrl/residences/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
setBody(request)
|
setBody(request)
|
||||||
}
|
}
|
||||||
@@ -52,17 +54,17 @@ class ResidenceApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to create residence", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_create_residence"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateResidence(token: String, id: Int, request: ResidenceCreateRequest): ApiResult<WithSummaryResponse<ResidenceResponse>> {
|
suspend fun updateResidence(token: String, id: Int, request: ResidenceCreateRequest): ApiResult<WithSummaryResponse<ResidenceResponse>> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.put("$baseUrl/residences/$id/") {
|
val response = client.put("$baseUrl/residences/$id/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
setBody(request)
|
setBody(request)
|
||||||
}
|
}
|
||||||
@@ -70,58 +72,58 @@ class ResidenceApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to update residence", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_update_residence"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteResidence(token: String, id: Int): ApiResult<WithSummaryResponse<String>> {
|
suspend fun deleteResidence(token: String, id: Int): ApiResult<WithSummaryResponse<String>> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.delete("$baseUrl/residences/$id/") {
|
val response = client.delete("$baseUrl/residences/$id/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to delete residence", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_delete_residence"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getSummary(token: String): ApiResult<TotalSummary> {
|
suspend fun getSummary(token: String): ApiResult<TotalSummary> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/residences/summary/") {
|
val response = client.get("$baseUrl/residences/summary/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to fetch summary", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_fetch_summary"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getMyResidences(token: String): ApiResult<MyResidencesResponse> {
|
suspend fun getMyResidences(token: String): ApiResult<MyResidencesResponse> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/residences/my-residences/") {
|
val response = client.get("$baseUrl/residences/my-residences/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to fetch my residences", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_fetch_my_residences"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +131,7 @@ class ResidenceApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
suspend fun generateSharePackage(token: String, residenceId: Int): ApiResult<SharedResidence> {
|
suspend fun generateSharePackage(token: String, residenceId: Int): ApiResult<SharedResidence> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.post("$baseUrl/residences/$residenceId/generate-share-package/") {
|
val response = client.post("$baseUrl/residences/$residenceId/generate-share-package/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
@@ -139,14 +141,14 @@ class ResidenceApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
ApiResult.Error(errorMessage, response.status.value)
|
ApiResult.Error(errorMessage, response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun generateShareCode(token: String, residenceId: Int): ApiResult<GenerateShareCodeResponse> {
|
suspend fun generateShareCode(token: String, residenceId: Int): ApiResult<GenerateShareCodeResponse> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.post("$baseUrl/residences/$residenceId/generate-share-code/") {
|
val response = client.post("$baseUrl/residences/$residenceId/generate-share-code/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
@@ -156,14 +158,14 @@ class ResidenceApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
ApiResult.Error(errorMessage, response.status.value)
|
ApiResult.Error(errorMessage, response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getShareCode(token: String, residenceId: Int): ApiResult<ShareCodeResponse> {
|
suspend fun getShareCode(token: String, residenceId: Int): ApiResult<ShareCodeResponse> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/residences/$residenceId/share-code/") {
|
val response = client.get("$baseUrl/residences/$residenceId/share-code/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
@@ -173,7 +175,7 @@ class ResidenceApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
ApiResult.Error(errorMessage, response.status.value)
|
ApiResult.Error(errorMessage, response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,7 +188,7 @@ class ResidenceApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
suspend fun acceptResidenceInvite(token: String, residenceId: Int): ApiResult<Unit> {
|
suspend fun acceptResidenceInvite(token: String, residenceId: Int): ApiResult<Unit> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.post("$baseUrl/residences/$residenceId/invite/accept/") {
|
val response = client.post("$baseUrl/residences/$residenceId/invite/accept/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
@@ -196,7 +198,7 @@ class ResidenceApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
ApiResult.Error(errorMessage, response.status.value)
|
ApiResult.Error(errorMessage, response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,7 +209,7 @@ class ResidenceApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
suspend fun declineResidenceInvite(token: String, residenceId: Int): ApiResult<Unit> {
|
suspend fun declineResidenceInvite(token: String, residenceId: Int): ApiResult<Unit> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.post("$baseUrl/residences/$residenceId/invite/decline/") {
|
val response = client.post("$baseUrl/residences/$residenceId/invite/decline/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
@@ -217,14 +219,14 @@ class ResidenceApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
ApiResult.Error(errorMessage, response.status.value)
|
ApiResult.Error(errorMessage, response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun joinWithCode(token: String, code: String): ApiResult<JoinResidenceResponse> {
|
suspend fun joinWithCode(token: String, code: String): ApiResult<JoinResidenceResponse> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.post("$baseUrl/residences/join-with-code/") {
|
val response = client.post("$baseUrl/residences/join-with-code/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
setBody(JoinResidenceRequest(code))
|
setBody(JoinResidenceRequest(code))
|
||||||
}
|
}
|
||||||
@@ -236,7 +238,7 @@ class ResidenceApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
ApiResult.Error(errorMessage, response.status.value)
|
ApiResult.Error(errorMessage, response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,7 +246,7 @@ class ResidenceApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
suspend fun getResidenceUsers(token: String, residenceId: Int): ApiResult<ResidenceUsersResponse> {
|
suspend fun getResidenceUsers(token: String, residenceId: Int): ApiResult<ResidenceUsersResponse> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/residences/$residenceId/users/") {
|
val response = client.get("$baseUrl/residences/$residenceId/users/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
@@ -254,14 +256,14 @@ class ResidenceApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
ApiResult.Error(errorMessage, response.status.value)
|
ApiResult.Error(errorMessage, response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun removeUser(token: String, residenceId: Int, userId: Int): ApiResult<RemoveUserResponse> {
|
suspend fun removeUser(token: String, residenceId: Int, userId: Int): ApiResult<RemoveUserResponse> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.delete("$baseUrl/residences/$residenceId/users/$userId/") {
|
val response = client.delete("$baseUrl/residences/$residenceId/users/$userId/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
@@ -271,7 +273,7 @@ class ResidenceApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
ApiResult.Error(errorMessage, response.status.value)
|
ApiResult.Error(errorMessage, response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,7 +281,7 @@ class ResidenceApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
suspend fun generateTasksReport(token: String, residenceId: Int, email: String? = null): ApiResult<GenerateReportResponse> {
|
suspend fun generateTasksReport(token: String, residenceId: Int, email: String? = null): ApiResult<GenerateReportResponse> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.post("$baseUrl/residences/$residenceId/generate-tasks-report/") {
|
val response = client.post("$baseUrl/residences/$residenceId/generate-tasks-report/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
if (email != null) {
|
if (email != null) {
|
||||||
setBody(mapOf("email" to email))
|
setBody(mapOf("email" to email))
|
||||||
@@ -293,7 +295,7 @@ class ResidenceApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
ApiResult.Error(errorMessage, response.status.value)
|
ApiResult.Error(errorMessage, response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.tt.honeyDue.network
|
package com.tt.honeyDue.network
|
||||||
|
|
||||||
|
import com.tt.honeyDue.i18n.ClientStrings
|
||||||
|
|
||||||
import com.tt.honeyDue.models.*
|
import com.tt.honeyDue.models.*
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.call.*
|
import io.ktor.client.call.*
|
||||||
@@ -12,16 +14,16 @@ class SubscriptionApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
suspend fun getSubscriptionStatus(token: String): ApiResult<SubscriptionStatus> {
|
suspend fun getSubscriptionStatus(token: String): ApiResult<SubscriptionStatus> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/subscription/status/") {
|
val response = client.get("$baseUrl/subscription/status/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to fetch subscription status", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_fetch_subscription_status"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,48 +31,48 @@ class SubscriptionApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/subscription/upgrade-triggers/") {
|
val response = client.get("$baseUrl/subscription/upgrade-triggers/") {
|
||||||
// Token is optional - endpoint is public
|
// Token is optional - endpoint is public
|
||||||
token?.let { header("Authorization", "Token $it") }
|
token?.let { header(SESSION_TOKEN_HEADER, it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to fetch upgrade triggers", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_fetch_upgrade_triggers"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getFeatureBenefits(token: String): ApiResult<List<FeatureBenefit>> {
|
suspend fun getFeatureBenefits(token: String): ApiResult<List<FeatureBenefit>> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/subscription/features/") {
|
val response = client.get("$baseUrl/subscription/features/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to fetch feature benefits", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_fetch_feature_benefits"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getActivePromotions(token: String): ApiResult<List<Promotion>> {
|
suspend fun getActivePromotions(token: String): ApiResult<List<Promotion>> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/subscription/promotions/") {
|
val response = client.get("$baseUrl/subscription/promotions/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to fetch promotions", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_fetch_promotions"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +87,7 @@ class SubscriptionApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
): ApiResult<VerificationResponse> {
|
): ApiResult<VerificationResponse> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.post("$baseUrl/subscription/purchase/") {
|
val response = client.post("$baseUrl/subscription/purchase/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
setBody(mapOf(
|
setBody(mapOf(
|
||||||
"platform" to "ios",
|
"platform" to "ios",
|
||||||
@@ -97,10 +99,10 @@ class SubscriptionApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to verify iOS receipt", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_verify_ios_receipt"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +117,7 @@ class SubscriptionApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
): ApiResult<VerificationResponse> {
|
): ApiResult<VerificationResponse> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.post("$baseUrl/subscription/purchase/") {
|
val response = client.post("$baseUrl/subscription/purchase/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
setBody(mapOf(
|
setBody(mapOf(
|
||||||
"platform" to "android",
|
"platform" to "android",
|
||||||
@@ -127,10 +129,10 @@ class SubscriptionApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to verify Android purchase", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_verify_android_purchase"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +156,7 @@ class SubscriptionApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val response = client.post("$baseUrl/subscription/restore/") {
|
val response = client.post("$baseUrl/subscription/restore/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
setBody(body)
|
setBody(body)
|
||||||
}
|
}
|
||||||
@@ -162,10 +164,10 @@ class SubscriptionApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to restore subscription", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_restore_subscription"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.tt.honeyDue.network
|
package com.tt.honeyDue.network
|
||||||
|
|
||||||
|
import com.tt.honeyDue.i18n.ClientStrings
|
||||||
|
|
||||||
import com.tt.honeyDue.models.*
|
import com.tt.honeyDue.models.*
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.call.*
|
import io.ktor.client.call.*
|
||||||
@@ -15,7 +17,7 @@ class TaskApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
): ApiResult<TaskColumnsResponse> {
|
): ApiResult<TaskColumnsResponse> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/tasks/") {
|
val response = client.get("$baseUrl/tasks/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
days?.let { parameter("days", it) }
|
days?.let { parameter("days", it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,14 +28,14 @@ class TaskApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
ApiResult.Error(errorMessage, response.status.value)
|
ApiResult.Error(errorMessage, response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getTask(token: String, id: Int): ApiResult<TaskResponse> {
|
suspend fun getTask(token: String, id: Int): ApiResult<TaskResponse> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/tasks/$id/") {
|
val response = client.get("$baseUrl/tasks/$id/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
@@ -43,14 +45,14 @@ class TaskApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
ApiResult.Error(errorMessage, response.status.value)
|
ApiResult.Error(errorMessage, response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun createTask(token: String, request: TaskCreateRequest): ApiResult<WithSummaryResponse<TaskResponse>> {
|
suspend fun createTask(token: String, request: TaskCreateRequest): ApiResult<WithSummaryResponse<TaskResponse>> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.post("$baseUrl/tasks/") {
|
val response = client.post("$baseUrl/tasks/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
setBody(request)
|
setBody(request)
|
||||||
}
|
}
|
||||||
@@ -62,7 +64,7 @@ class TaskApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
ApiResult.Error(errorMessage, response.status.value)
|
ApiResult.Error(errorMessage, response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +77,7 @@ class TaskApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
suspend fun bulkCreateTasks(token: String, request: BulkCreateTasksRequest): ApiResult<BulkCreateTasksResponse> {
|
suspend fun bulkCreateTasks(token: String, request: BulkCreateTasksRequest): ApiResult<BulkCreateTasksResponse> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.post("$baseUrl/tasks/bulk/") {
|
val response = client.post("$baseUrl/tasks/bulk/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
setBody(request)
|
setBody(request)
|
||||||
}
|
}
|
||||||
@@ -87,14 +89,14 @@ class TaskApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
ApiResult.Error(errorMessage, response.status.value)
|
ApiResult.Error(errorMessage, response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateTask(token: String, id: Int, request: TaskCreateRequest): ApiResult<WithSummaryResponse<TaskResponse>> {
|
suspend fun updateTask(token: String, id: Int, request: TaskCreateRequest): ApiResult<WithSummaryResponse<TaskResponse>> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.put("$baseUrl/tasks/$id/") {
|
val response = client.put("$baseUrl/tasks/$id/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
setBody(request)
|
setBody(request)
|
||||||
}
|
}
|
||||||
@@ -106,14 +108,14 @@ class TaskApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
ApiResult.Error(errorMessage, response.status.value)
|
ApiResult.Error(errorMessage, response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteTask(token: String, id: Int): ApiResult<WithSummaryResponse<String>> {
|
suspend fun deleteTask(token: String, id: Int): ApiResult<WithSummaryResponse<String>> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.delete("$baseUrl/tasks/$id/") {
|
val response = client.delete("$baseUrl/tasks/$id/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
@@ -123,7 +125,7 @@ class TaskApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
ApiResult.Error(errorMessage, response.status.value)
|
ApiResult.Error(errorMessage, response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,7 +136,7 @@ class TaskApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
): ApiResult<TaskColumnsResponse> {
|
): ApiResult<TaskColumnsResponse> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/tasks/by-residence/$residenceId/") {
|
val response = client.get("$baseUrl/tasks/by-residence/$residenceId/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
days?.let { parameter("days", it) }
|
days?.let { parameter("days", it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +147,7 @@ class TaskApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
ApiResult.Error(errorMessage, response.status.value)
|
ApiResult.Error(errorMessage, response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +159,7 @@ class TaskApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
suspend fun patchTask(token: String, id: Int, request: TaskPatchRequest): ApiResult<WithSummaryResponse<TaskResponse>> {
|
suspend fun patchTask(token: String, id: Int, request: TaskPatchRequest): ApiResult<WithSummaryResponse<TaskResponse>> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.patch("$baseUrl/tasks/$id/") {
|
val response = client.patch("$baseUrl/tasks/$id/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
setBody(request)
|
setBody(request)
|
||||||
}
|
}
|
||||||
@@ -169,7 +171,7 @@ class TaskApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
ApiResult.Error(errorMessage, response.status.value)
|
ApiResult.Error(errorMessage, response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,7 +208,7 @@ class TaskApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
private suspend fun postTaskAction(token: String, id: Int, action: String): ApiResult<WithSummaryResponse<TaskResponse>> {
|
private suspend fun postTaskAction(token: String, id: Int, action: String): ApiResult<WithSummaryResponse<TaskResponse>> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.post("$baseUrl/tasks/$id/$action/") {
|
val response = client.post("$baseUrl/tasks/$id/$action/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
}
|
}
|
||||||
when (response.status) {
|
when (response.status) {
|
||||||
@@ -214,16 +216,16 @@ class TaskApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
val data = response.body<WithSummaryResponse<TaskResponse>>()
|
val data = response.body<WithSummaryResponse<TaskResponse>>()
|
||||||
ApiResult.Success(data)
|
ApiResult.Success(data)
|
||||||
}
|
}
|
||||||
HttpStatusCode.NotFound -> ApiResult.Error("Task not found", 404)
|
HttpStatusCode.NotFound -> ApiResult.Error(ClientStrings.t("err.api.task_not_found"), 404)
|
||||||
HttpStatusCode.Forbidden -> ApiResult.Error("Access denied", 403)
|
HttpStatusCode.Forbidden -> ApiResult.Error(ClientStrings.t("err.api.access_denied"), 403)
|
||||||
HttpStatusCode.BadRequest -> {
|
HttpStatusCode.BadRequest -> {
|
||||||
val errorBody = response.body<String>()
|
val errorBody = response.body<String>()
|
||||||
ApiResult.Error(errorBody, 400)
|
ApiResult.Error(errorBody, 400)
|
||||||
}
|
}
|
||||||
else -> ApiResult.Error("Task $action failed: ${response.status}", response.status.value)
|
else -> ApiResult.Error(ClientStrings.t("err.api.task_action_failed", action, response.status.toString()), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,7 +235,7 @@ class TaskApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
suspend fun getTaskCompletions(token: String, taskId: Int): ApiResult<List<TaskCompletionResponse>> {
|
suspend fun getTaskCompletions(token: String, taskId: Int): ApiResult<List<TaskCompletionResponse>> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/tasks/$taskId/completions/") {
|
val response = client.get("$baseUrl/tasks/$taskId/completions/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
@@ -243,7 +245,7 @@ class TaskApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
ApiResult.Error(errorMessage, response.status.value)
|
ApiResult.Error(errorMessage, response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.tt.honeyDue.network
|
package com.tt.honeyDue.network
|
||||||
|
|
||||||
|
import com.tt.honeyDue.i18n.ClientStrings
|
||||||
|
|
||||||
import com.tt.honeyDue.models.*
|
import com.tt.honeyDue.models.*
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.call.*
|
import io.ktor.client.call.*
|
||||||
@@ -13,39 +15,39 @@ class TaskCompletionApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
suspend fun getCompletions(token: String): ApiResult<List<TaskCompletionResponse>> {
|
suspend fun getCompletions(token: String): ApiResult<List<TaskCompletionResponse>> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/task-completions/") {
|
val response = client.get("$baseUrl/task-completions/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to fetch completions", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_fetch_completions"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getCompletion(token: String, id: Int): ApiResult<TaskCompletionResponse> {
|
suspend fun getCompletion(token: String, id: Int): ApiResult<TaskCompletionResponse> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/task-completions/$id/") {
|
val response = client.get("$baseUrl/task-completions/$id/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to fetch completion", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_fetch_completion"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun createCompletion(token: String, request: TaskCompletionCreateRequest): ApiResult<WithSummaryResponse<TaskCompletionResponse>> {
|
suspend fun createCompletion(token: String, request: TaskCompletionCreateRequest): ApiResult<WithSummaryResponse<TaskCompletionResponse>> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.post("$baseUrl/task-completions/") {
|
val response = client.post("$baseUrl/task-completions/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
setBody(request)
|
setBody(request)
|
||||||
}
|
}
|
||||||
@@ -53,17 +55,17 @@ class TaskCompletionApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to create completion", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_create_completion"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateCompletion(token: String, id: Int, request: TaskCompletionCreateRequest): ApiResult<TaskCompletionResponse> {
|
suspend fun updateCompletion(token: String, id: Int, request: TaskCompletionCreateRequest): ApiResult<TaskCompletionResponse> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.put("$baseUrl/task-completions/$id/") {
|
val response = client.put("$baseUrl/task-completions/$id/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
setBody(request)
|
setBody(request)
|
||||||
}
|
}
|
||||||
@@ -71,26 +73,26 @@ class TaskCompletionApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to update completion", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_update_completion"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteCompletion(token: String, id: Int): ApiResult<WithSummaryResponse<String>> {
|
suspend fun deleteCompletion(token: String, id: Int): ApiResult<WithSummaryResponse<String>> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.delete("$baseUrl/task-completions/$id/") {
|
val response = client.delete("$baseUrl/task-completions/$id/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to delete completion", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_delete_completion"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.tt.honeyDue.network
|
package com.tt.honeyDue.network
|
||||||
|
|
||||||
|
import com.tt.honeyDue.i18n.ClientStrings
|
||||||
|
|
||||||
import com.tt.honeyDue.models.TaskTemplate
|
import com.tt.honeyDue.models.TaskTemplate
|
||||||
import com.tt.honeyDue.models.TaskSuggestionsResponse
|
import com.tt.honeyDue.models.TaskSuggestionsResponse
|
||||||
import com.tt.honeyDue.models.TaskTemplatesGroupedResponse
|
import com.tt.honeyDue.models.TaskTemplatesGroupedResponse
|
||||||
@@ -25,10 +27,10 @@ class TaskTemplateApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to fetch task templates", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_fetch_task_templates"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,10 +44,10 @@ class TaskTemplateApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to fetch grouped task templates", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_fetch_grouped_task_templates"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,10 +63,10 @@ class TaskTemplateApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to search task templates", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_search_task_templates"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,10 +80,10 @@ class TaskTemplateApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to fetch templates by category", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_fetch_templates_by_category"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,17 +94,17 @@ class TaskTemplateApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
suspend fun getTaskSuggestions(token: String, residenceId: Int): ApiResult<TaskSuggestionsResponse> {
|
suspend fun getTaskSuggestions(token: String, residenceId: Int): ApiResult<TaskSuggestionsResponse> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get("$baseUrl/tasks/suggestions/") {
|
val response = client.get("$baseUrl/tasks/suggestions/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
parameter("residence_id", residenceId)
|
parameter("residence_id", residenceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Failed to fetch task suggestions", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.failed_fetch_task_suggestions"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,10 +118,10 @@ class TaskTemplateApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(response.body())
|
ApiResult.Success(response.body())
|
||||||
} else {
|
} else {
|
||||||
ApiResult.Error("Template not found", response.status.value)
|
ApiResult.Error(ClientStrings.t("err.api.template_not_found"), response.status.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: ClientStrings.t("err.unknown_occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package com.tt.honeyDue.network
|
package com.tt.honeyDue.network
|
||||||
|
|
||||||
|
import com.tt.honeyDue.i18n.ClientStrings
|
||||||
import com.tt.honeyDue.models.PresignUploadRequest
|
import com.tt.honeyDue.models.PresignUploadRequest
|
||||||
import com.tt.honeyDue.models.PresignUploadResponse
|
import com.tt.honeyDue.models.PresignUploadResponse
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.call.*
|
import io.ktor.client.call.*
|
||||||
import io.ktor.client.request.*
|
import io.ktor.client.request.*
|
||||||
import io.ktor.client.request.forms.*
|
|
||||||
import io.ktor.client.statement.*
|
import io.ktor.client.statement.*
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import io.ktor.utils.io.core.*
|
import io.ktor.utils.io.core.*
|
||||||
@@ -14,17 +14,16 @@ import io.ktor.utils.io.core.*
|
|||||||
* Three-step direct-to-B2 upload helper.
|
* Three-step direct-to-B2 upload helper.
|
||||||
*
|
*
|
||||||
* Step 1: [presign] — call POST /api/uploads/presign on our API. Returns a
|
* Step 1: [presign] — call POST /api/uploads/presign on our API. Returns a
|
||||||
* B2 POST policy plus form fields the client needs to perform the
|
* signed PUT URL plus the headers the client must send.
|
||||||
* direct upload.
|
* Step 2: [putToStorage] — single PUT straight to B2. Bytes never traverse
|
||||||
* Step 2: [postToStorage] — multipart/form-data POST straight to B2.
|
* our API server.
|
||||||
* Bytes never traverse our API server.
|
|
||||||
* Step 3: caller invokes the relevant entity-creation endpoint
|
* Step 3: caller invokes the relevant entity-creation endpoint
|
||||||
* (POST /api/task-completions/, POST /api/documents/) with the
|
* (POST /api/task-completions/, POST /api/documents/) with the
|
||||||
* returned upload_id in the `upload_ids` field.
|
* returned upload_id in the `upload_ids` field.
|
||||||
*
|
*
|
||||||
* iOS uses its own native equivalent (PresignedUploader.swift) for memory
|
* iOS uses its own native equivalent (PresignedUploader.swift). Both paths
|
||||||
* reasons — Swift can stream a multipart body without buffering. Android
|
* use PUT because B2's S3-compatible endpoint does not implement the S3
|
||||||
* uses this Kotlin path which works fine for ≤10 MB images.
|
* POST Object form upload (returns HTTP 501 for any POST).
|
||||||
*/
|
*/
|
||||||
class UploadApi(private val client: HttpClient = ApiClient.httpClient) {
|
class UploadApi(private val client: HttpClient = ApiClient.httpClient) {
|
||||||
private val baseUrl = ApiClient.getBaseUrl()
|
private val baseUrl = ApiClient.getBaseUrl()
|
||||||
@@ -38,7 +37,7 @@ class UploadApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
): ApiResult<PresignUploadResponse> {
|
): ApiResult<PresignUploadResponse> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.post("$baseUrl/uploads/presign/") {
|
val response = client.post("$baseUrl/uploads/presign/") {
|
||||||
header("Authorization", "Token $token")
|
header(SESSION_TOKEN_HEADER, token)
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
setBody(PresignUploadRequest(category, contentType, contentLength))
|
setBody(PresignUploadRequest(category, contentType, contentLength))
|
||||||
}
|
}
|
||||||
@@ -47,52 +46,50 @@ class UploadApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
} else {
|
} else {
|
||||||
ApiResult.Error(
|
ApiResult.Error(
|
||||||
when (response.status.value) {
|
when (response.status.value) {
|
||||||
413 -> "That photo is too large after resizing."
|
413 -> ClientStrings.t("upload.too_large")
|
||||||
422 -> "That image format isn't supported."
|
422 -> ClientStrings.t("upload.unsupported")
|
||||||
429 -> "Too many uploads in flight; try again shortly."
|
429 -> ClientStrings.t("upload.too_many")
|
||||||
else -> "Couldn't start upload (HTTP ${response.status.value})."
|
else -> ClientStrings.t("upload.start_failed")
|
||||||
},
|
},
|
||||||
response.status.value,
|
response.status.value,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Network error during presign")
|
ApiResult.Error(e.message ?: ClientStrings.t("upload.net_presign"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Step 2 — POST `data` directly to B2 using the signed policy fields.
|
* Step 2 — PUT `data` directly to B2 using the signed URL + headers.
|
||||||
*
|
*
|
||||||
* The S3 POST policy spec requires every signed field to appear before
|
* The presign signature binds the headers exactly, so we send them
|
||||||
* the file part, and `key` + `Content-Type` must match the policy
|
* verbatim. Content-Length is filled in automatically by Ktor from
|
||||||
* exactly. Ktor's MultiPartFormDataContent preserves insertion order
|
* the body size, but we still pass through Content-Type which Ktor
|
||||||
* for the appended parts.
|
* would otherwise default to application/octet-stream.
|
||||||
*/
|
*/
|
||||||
suspend fun postToStorage(
|
suspend fun putToStorage(
|
||||||
uploadUrl: String,
|
uploadUrl: String,
|
||||||
fields: Map<String, String>,
|
headers: Map<String, String>,
|
||||||
data: ByteArray,
|
data: ByteArray,
|
||||||
contentType: String,
|
contentType: String,
|
||||||
fileName: String,
|
|
||||||
): ApiResult<Unit> {
|
): ApiResult<Unit> {
|
||||||
return try {
|
return try {
|
||||||
val parts = formData {
|
val response = client.put(uploadUrl) {
|
||||||
// Stable order: signed fields first, then file. We rely on
|
// Apply server-supplied headers verbatim. Skip Content-Length
|
||||||
// Ktor preserving the order in which append() is called.
|
// — Ktor sets it automatically from the body and will refuse
|
||||||
fields.forEach { (k, v) -> append(k, v) }
|
// a manual override on most engines.
|
||||||
append(
|
headers.forEach { (k, v) ->
|
||||||
key = "file",
|
if (!k.equals("Content-Length", ignoreCase = true)) {
|
||||||
value = data,
|
header(k, v)
|
||||||
headers = Headers.build {
|
}
|
||||||
append(HttpHeaders.ContentDisposition, "filename=\"$fileName\"")
|
}
|
||||||
append(HttpHeaders.ContentType, contentType)
|
// Defensive: ensure Content-Type is set even if the server
|
||||||
},
|
// omits it. The signed value (if present) takes precedence.
|
||||||
)
|
if (!headers.keys.any { it.equals("Content-Type", ignoreCase = true) }) {
|
||||||
|
header(HttpHeaders.ContentType, contentType)
|
||||||
|
}
|
||||||
|
setBody(data)
|
||||||
}
|
}
|
||||||
val response = client.submitFormWithBinaryData(
|
|
||||||
url = uploadUrl,
|
|
||||||
formData = parts,
|
|
||||||
)
|
|
||||||
if (response.status.isSuccess()) {
|
if (response.status.isSuccess()) {
|
||||||
ApiResult.Success(Unit)
|
ApiResult.Success(Unit)
|
||||||
} else {
|
} else {
|
||||||
@@ -102,12 +99,12 @@ class UploadApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
""
|
""
|
||||||
}
|
}
|
||||||
ApiResult.Error(
|
ApiResult.Error(
|
||||||
"Upload to storage failed (HTTP ${response.status.value}): $body",
|
ClientStrings.t("upload.storage_failed"),
|
||||||
response.status.value,
|
response.status.value,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ApiResult.Error(e.message ?: "Network error during upload")
|
ApiResult.Error(e.message ?: ClientStrings.t("upload.net_upload"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,26 +121,25 @@ class UploadApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
category: String,
|
category: String,
|
||||||
contentType: String,
|
contentType: String,
|
||||||
data: ByteArray,
|
data: ByteArray,
|
||||||
fileName: String,
|
@Suppress("UNUSED_PARAMETER") fileName: String,
|
||||||
): ApiResult<Int> {
|
): ApiResult<Int> {
|
||||||
val presignResult = presign(token, category, contentType, data.size.toLong())
|
val presignResult = presign(token, category, contentType, data.size.toLong())
|
||||||
val presigned = (presignResult as? ApiResult.Success)?.data
|
val presigned = (presignResult as? ApiResult.Success)?.data
|
||||||
?: return ApiResult.Error(
|
?: return ApiResult.Error(
|
||||||
(presignResult as? ApiResult.Error)?.message ?: "Presign failed",
|
(presignResult as? ApiResult.Error)?.message ?: ClientStrings.t("upload.failed"),
|
||||||
(presignResult as? ApiResult.Error)?.code,
|
(presignResult as? ApiResult.Error)?.code,
|
||||||
)
|
)
|
||||||
|
|
||||||
val postResult = postToStorage(
|
val putResult = putToStorage(
|
||||||
uploadUrl = presigned.uploadUrl,
|
uploadUrl = presigned.uploadUrl,
|
||||||
fields = presigned.fields,
|
headers = presigned.headers,
|
||||||
data = data,
|
data = data,
|
||||||
contentType = contentType,
|
contentType = contentType,
|
||||||
fileName = fileName,
|
|
||||||
)
|
)
|
||||||
return when (postResult) {
|
return when (putResult) {
|
||||||
is ApiResult.Success -> ApiResult.Success(presigned.id)
|
is ApiResult.Success -> ApiResult.Success(presigned.id)
|
||||||
is ApiResult.Error -> postResult
|
is ApiResult.Error -> putResult
|
||||||
else -> ApiResult.Error("Upload failed in unknown state")
|
else -> ApiResult.Error(ClientStrings.t("upload.failed"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ object TaskAnimations {
|
|||||||
* iOS: `.spring(response: 0.4, dampingFraction: 0.6)`
|
* iOS: `.spring(response: 0.4, dampingFraction: 0.6)`
|
||||||
*/
|
*/
|
||||||
val completionCheckmark = AnimationSpecValues(
|
val completionCheckmark = AnimationSpecValues(
|
||||||
name = "completionCheckmark",
|
name = "completionCheckmark", // i18n-ignore: animation transition label, non-UI
|
||||||
durationMillis = 400,
|
durationMillis = 400,
|
||||||
easing = Easing.SPRING,
|
easing = Easing.SPRING,
|
||||||
springResponseMillis = 400,
|
springResponseMillis = 400,
|
||||||
@@ -150,7 +150,7 @@ object TaskAnimations {
|
|||||||
* exit starts).
|
* exit starts).
|
||||||
*/
|
*/
|
||||||
val cardEnter = AnimationSpecValues(
|
val cardEnter = AnimationSpecValues(
|
||||||
name = "cardEnter",
|
name = "cardEnter", // i18n-ignore: animation transition label, non-UI
|
||||||
durationMillis = 350,
|
durationMillis = 350,
|
||||||
easing = Easing.SPRING,
|
easing = Easing.SPRING,
|
||||||
springResponseMillis = 350,
|
springResponseMillis = 350,
|
||||||
@@ -162,7 +162,7 @@ object TaskAnimations {
|
|||||||
* iOS: `.easeIn(duration: 0.3)` on the card shrink+fade.
|
* iOS: `.easeIn(duration: 0.3)` on the card shrink+fade.
|
||||||
*/
|
*/
|
||||||
val cardDismiss = AnimationSpecValues(
|
val cardDismiss = AnimationSpecValues(
|
||||||
name = "cardDismiss",
|
name = "cardDismiss", // i18n-ignore: animation transition label, non-UI
|
||||||
durationMillis = 300,
|
durationMillis = 300,
|
||||||
easing = Easing.EASE_IN,
|
easing = Easing.EASE_IN,
|
||||||
)
|
)
|
||||||
@@ -174,7 +174,7 @@ object TaskAnimations {
|
|||||||
* pulse so the motion reads as "breathing" at ~50bpm.
|
* pulse so the motion reads as "breathing" at ~50bpm.
|
||||||
*/
|
*/
|
||||||
val priorityPulse = LoopSpecValues(
|
val priorityPulse = LoopSpecValues(
|
||||||
name = "priorityPulse",
|
name = "priorityPulse", // i18n-ignore: animation transition label, non-UI
|
||||||
periodMillis = 1200,
|
periodMillis = 1200,
|
||||||
easing = Easing.EASE_IN_OUT,
|
easing = Easing.EASE_IN_OUT,
|
||||||
reverses = true,
|
reverses = true,
|
||||||
@@ -186,7 +186,7 @@ object TaskAnimations {
|
|||||||
* cycle; we match that for the loading shimmer.
|
* cycle; we match that for the loading shimmer.
|
||||||
*/
|
*/
|
||||||
val honeycombLoop = LoopSpecValues(
|
val honeycombLoop = LoopSpecValues(
|
||||||
name = "honeycombLoop",
|
name = "honeycombLoop", // i18n-ignore: animation transition label, non-UI
|
||||||
periodMillis = 8000,
|
periodMillis = 8000,
|
||||||
easing = Easing.LINEAR,
|
easing = Easing.LINEAR,
|
||||||
reverses = false,
|
reverses = false,
|
||||||
|
|||||||
@@ -294,7 +294,7 @@ fun AddTaskDialog(
|
|||||||
frequency = freq
|
frequency = freq
|
||||||
showFrequencyDropdown = false
|
showFrequencyDropdown = false
|
||||||
// Clear interval days if frequency is not "Custom"
|
// Clear interval days if frequency is not "Custom"
|
||||||
if (!freq.name.equals("Custom", ignoreCase = true)) {
|
if (!freq.name.equals("Custom", ignoreCase = true)) { // i18n-ignore: API enum value comparison, non-UI
|
||||||
intervalDays = ""
|
intervalDays = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -304,7 +304,7 @@ fun AddTaskDialog(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Custom Interval Days (only for "Custom" frequency)
|
// Custom Interval Days (only for "Custom" frequency)
|
||||||
if (frequency.name.equals("Custom", ignoreCase = true)) {
|
if (frequency.name.equals("Custom", ignoreCase = true)) { // i18n-ignore: API enum value comparison, non-UI
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = intervalDays,
|
value = intervalDays,
|
||||||
onValueChange = { intervalDays = it.filter { char -> char.isDigit() } },
|
onValueChange = { intervalDays = it.filter { char -> char.isDigit() } },
|
||||||
@@ -425,7 +425,7 @@ fun AddTaskDialog(
|
|||||||
description = description.ifBlank { null },
|
description = description.ifBlank { null },
|
||||||
categoryId = if (category.id > 0) category.id else null,
|
categoryId = if (category.id > 0) category.id else null,
|
||||||
frequencyId = if (frequency.id > 0) frequency.id else null,
|
frequencyId = if (frequency.id > 0) frequency.id else null,
|
||||||
customIntervalDays = if (frequency.name.equals("Custom", ignoreCase = true) && intervalDays.isNotBlank()) {
|
customIntervalDays = if (frequency.name.equals("Custom", ignoreCase = true) && intervalDays.isNotBlank()) { // i18n-ignore: API enum value comparison, non-UI
|
||||||
intervalDays.toIntOrNull()
|
intervalDays.toIntOrNull()
|
||||||
} else null,
|
} else null,
|
||||||
priorityId = if (priority.id > 0) priority.id else null,
|
priorityId = if (priority.id > 0) priority.id else null,
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import androidx.compose.runtime.*
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import com.tt.honeyDue.network.ApiResult
|
import com.tt.honeyDue.network.ApiResult
|
||||||
|
import honeydue.composeapp.generated.resources.Res
|
||||||
|
import honeydue.composeapp.generated.resources.error_network_title
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles ApiResult states automatically with loading, error dialogs, and success content.
|
* Handles ApiResult states automatically with loading, error dialogs, and success content.
|
||||||
@@ -38,9 +41,10 @@ fun <T> ApiResultHandler(
|
|||||||
onRetry: () -> Unit,
|
onRetry: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
loadingContent: @Composable (() -> Unit)? = null,
|
loadingContent: @Composable (() -> Unit)? = null,
|
||||||
errorTitle: String = "Network Error",
|
errorTitle: String? = null,
|
||||||
content: @Composable (T) -> Unit
|
content: @Composable (T) -> Unit
|
||||||
) {
|
) {
|
||||||
|
val resolvedErrorTitle = errorTitle ?: stringResource(Res.string.error_network_title)
|
||||||
var showErrorDialog by remember { mutableStateOf(false) }
|
var showErrorDialog by remember { mutableStateOf(false) }
|
||||||
var errorMessage by remember { mutableStateOf("") }
|
var errorMessage by remember { mutableStateOf("") }
|
||||||
|
|
||||||
@@ -84,7 +88,7 @@ fun <T> ApiResultHandler(
|
|||||||
// Error dialog
|
// Error dialog
|
||||||
if (showErrorDialog) {
|
if (showErrorDialog) {
|
||||||
ErrorDialog(
|
ErrorDialog(
|
||||||
title = errorTitle,
|
title = resolvedErrorTitle,
|
||||||
message = errorMessage,
|
message = errorMessage,
|
||||||
onRetry = {
|
onRetry = {
|
||||||
showErrorDialog = false
|
showErrorDialog = false
|
||||||
@@ -120,8 +124,9 @@ fun <T> ApiResultHandler(
|
|||||||
@Composable
|
@Composable
|
||||||
fun <T> ApiResult<T>.HandleErrors(
|
fun <T> ApiResult<T>.HandleErrors(
|
||||||
onRetry: () -> Unit,
|
onRetry: () -> Unit,
|
||||||
errorTitle: String = "Network Error"
|
errorTitle: String? = null
|
||||||
) {
|
) {
|
||||||
|
val resolvedErrorTitle = errorTitle ?: stringResource(Res.string.error_network_title)
|
||||||
var showErrorDialog by remember { mutableStateOf(false) }
|
var showErrorDialog by remember { mutableStateOf(false) }
|
||||||
var errorMessage by remember { mutableStateOf("") }
|
var errorMessage by remember { mutableStateOf("") }
|
||||||
|
|
||||||
@@ -134,7 +139,7 @@ fun <T> ApiResult<T>.HandleErrors(
|
|||||||
|
|
||||||
if (showErrorDialog) {
|
if (showErrorDialog) {
|
||||||
ErrorDialog(
|
ErrorDialog(
|
||||||
title = errorTitle,
|
title = resolvedErrorTitle,
|
||||||
message = errorMessage,
|
message = errorMessage,
|
||||||
onRetry = {
|
onRetry = {
|
||||||
showErrorDialog = false
|
showErrorDialog = false
|
||||||
|
|||||||
+8
-3
@@ -17,7 +17,10 @@ import coil3.request.ImageRequest
|
|||||||
import coil3.network.NetworkHeaders
|
import coil3.network.NetworkHeaders
|
||||||
import coil3.network.httpHeaders
|
import coil3.network.httpHeaders
|
||||||
import com.tt.honeyDue.network.ApiClient
|
import com.tt.honeyDue.network.ApiClient
|
||||||
|
import com.tt.honeyDue.network.SESSION_TOKEN_HEADER
|
||||||
import com.tt.honeyDue.storage.TokenStorage
|
import com.tt.honeyDue.storage.TokenStorage
|
||||||
|
import honeydue.composeapp.generated.resources.*
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Compose component that loads images from authenticated API endpoints.
|
* A Compose component that loads images from authenticated API endpoints.
|
||||||
@@ -57,9 +60,11 @@ fun AuthenticatedImage(
|
|||||||
.data(fullUrl)
|
.data(fullUrl)
|
||||||
.apply {
|
.apply {
|
||||||
if (token != null) {
|
if (token != null) {
|
||||||
|
// honeyDue media is gated on the Kratos session token,
|
||||||
|
// carried on the X-Session-Token header.
|
||||||
httpHeaders(
|
httpHeaders(
|
||||||
NetworkHeaders.Builder()
|
NetworkHeaders.Builder()
|
||||||
.set("Authorization", "Token $token")
|
.set(SESSION_TOKEN_HEADER, token)
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -111,13 +116,13 @@ private fun DefaultErrorContent() {
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.BrokenImage,
|
Icons.Default.BrokenImage,
|
||||||
contentDescription = "Failed to load",
|
contentDescription = stringResource(Res.string.image_failed_to_load),
|
||||||
modifier = Modifier.size(40.dp),
|
modifier = Modifier.size(40.dp),
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
Text(
|
Text(
|
||||||
"Failed to load",
|
stringResource(Res.string.image_failed_to_load),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
|
|||||||
+6
-4
@@ -88,6 +88,8 @@ fun CompleteTaskDialog(
|
|||||||
val noneManualEntry = stringResource(Res.string.completions_none_manual)
|
val noneManualEntry = stringResource(Res.string.completions_none_manual)
|
||||||
val cancelText = stringResource(Res.string.common_cancel)
|
val cancelText = stringResource(Res.string.common_cancel)
|
||||||
val removeImageDesc = stringResource(Res.string.completions_remove_image)
|
val removeImageDesc = stringResource(Res.string.completions_remove_image)
|
||||||
|
val contractorPrefix = stringResource(Res.string.completions_contractor_prefix)
|
||||||
|
val completedByPrefix = stringResource(Res.string.completions_completed_by_prefix)
|
||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
@@ -239,7 +241,7 @@ fun CompleteTaskDialog(
|
|||||||
val starColor by animateColorAsState(
|
val starColor by animateColorAsState(
|
||||||
targetValue = if (isSelected) Color(0xFFFFD700) else MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.3f),
|
targetValue = if (isSelected) Color(0xFFFFD700) else MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.3f),
|
||||||
animationSpec = tween(durationMillis = 150),
|
animationSpec = tween(durationMillis = 150),
|
||||||
label = "starColor"
|
label = "starColor" // i18n-ignore: animation label, non-UI
|
||||||
)
|
)
|
||||||
|
|
||||||
IconButton(
|
IconButton(
|
||||||
@@ -251,7 +253,7 @@ fun CompleteTaskDialog(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = if (isSelected) Icons.Default.Star else Icons.Default.StarOutline,
|
imageVector = if (isSelected) Icons.Default.Star else Icons.Default.StarOutline,
|
||||||
contentDescription = "$star stars",
|
contentDescription = stringResource(Res.string.completions_star_rating_cd, star),
|
||||||
tint = starColor,
|
tint = starColor,
|
||||||
modifier = Modifier.size(32.dp)
|
modifier = Modifier.size(32.dp)
|
||||||
)
|
)
|
||||||
@@ -360,10 +362,10 @@ fun CompleteTaskDialog(
|
|||||||
// Build notes with contractor info if selected
|
// Build notes with contractor info if selected
|
||||||
val notesWithContractor = buildString {
|
val notesWithContractor = buildString {
|
||||||
if (selectedContractorName != null) {
|
if (selectedContractorName != null) {
|
||||||
append("Contractor: $selectedContractorName\n")
|
append("$contractorPrefix $selectedContractorName\n") // i18n-ignore: localized prefix var + data concatenation, no literal prose
|
||||||
}
|
}
|
||||||
if (completedByName.isNotBlank()) {
|
if (completedByName.isNotBlank()) {
|
||||||
append("Completed by: $completedByName\n")
|
append("$completedByPrefix $completedByName\n") // i18n-ignore: localized prefix var + data concatenation, no literal prose
|
||||||
}
|
}
|
||||||
if (notes.isNotBlank()) {
|
if (notes.isNotBlank()) {
|
||||||
append(notes)
|
append(notes)
|
||||||
|
|||||||
+2
-2
@@ -156,7 +156,7 @@ fun ContractorImportSuccessDialog(
|
|||||||
icon = {
|
icon = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.CheckCircle,
|
imageVector = Icons.Default.CheckCircle,
|
||||||
contentDescription = "Success",
|
contentDescription = stringResource(Res.string.common_success),
|
||||||
modifier = Modifier.size(48.dp),
|
modifier = Modifier.size(48.dp),
|
||||||
tint = MaterialTheme.colorScheme.primary
|
tint = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
@@ -202,7 +202,7 @@ fun ContractorImportErrorDialog(
|
|||||||
icon = {
|
icon = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Error,
|
imageVector = Icons.Default.Error,
|
||||||
contentDescription = "Error",
|
contentDescription = stringResource(Res.string.common_error),
|
||||||
modifier = Modifier.size(48.dp),
|
modifier = Modifier.size(48.dp),
|
||||||
tint = MaterialTheme.colorScheme.error
|
tint = MaterialTheme.colorScheme.error
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import com.tt.honeyDue.ui.haptics.Haptics
|
import com.tt.honeyDue.ui.haptics.Haptics
|
||||||
|
import honeydue.composeapp.generated.resources.Res
|
||||||
|
import honeydue.composeapp.generated.resources.error_network_title
|
||||||
|
import honeydue.composeapp.generated.resources.common_try_again
|
||||||
|
import honeydue.composeapp.generated.resources.common_cancel
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reusable error dialog component that shows network errors with retry/cancel options
|
* Reusable error dialog component that shows network errors with retry/cancel options
|
||||||
@@ -23,12 +28,12 @@ import com.tt.honeyDue.ui.haptics.Haptics
|
|||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun ErrorDialog(
|
fun ErrorDialog(
|
||||||
title: String = "Network Error",
|
title: String = stringResource(Res.string.error_network_title),
|
||||||
message: String,
|
message: String,
|
||||||
onRetry: () -> Unit,
|
onRetry: () -> Unit,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
retryButtonText: String = "Try Again",
|
retryButtonText: String = stringResource(Res.string.common_try_again),
|
||||||
dismissButtonText: String = "Cancel"
|
dismissButtonText: String = stringResource(Res.string.common_cancel)
|
||||||
) {
|
) {
|
||||||
// P5 Stream S — error haptic when the dialog appears
|
// P5 Stream S — error haptic when the dialog appears
|
||||||
LaunchedEffect(message) { Haptics.error() }
|
LaunchedEffect(message) { Haptics.error() }
|
||||||
|
|||||||
+20
-18
@@ -25,6 +25,8 @@ import com.tt.honeyDue.models.ResidenceShareCode
|
|||||||
import com.tt.honeyDue.network.ApiResult
|
import com.tt.honeyDue.network.ApiResult
|
||||||
import com.tt.honeyDue.network.APILayer
|
import com.tt.honeyDue.network.APILayer
|
||||||
import com.tt.honeyDue.ui.theme.AppSpacing
|
import com.tt.honeyDue.ui.theme.AppSpacing
|
||||||
|
import honeydue.composeapp.generated.resources.*
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -83,10 +85,10 @@ fun ManageUsersDialog(
|
|||||||
modifier = Modifier.size(28.dp)
|
modifier = Modifier.size(28.dp)
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(AppSpacing.sm))
|
Spacer(modifier = Modifier.width(AppSpacing.sm))
|
||||||
Text("Invite Others")
|
Text(stringResource(Res.string.manage_users_invite_title))
|
||||||
}
|
}
|
||||||
IconButton(onClick = onDismiss) {
|
IconButton(onClick = onDismiss) {
|
||||||
Icon(Icons.Default.Close, "Close")
|
Icon(Icons.Default.Close, stringResource(Res.string.a11y_close))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -101,7 +103,7 @@ fun ManageUsersDialog(
|
|||||||
}
|
}
|
||||||
} else if (error != null) {
|
} else if (error != null) {
|
||||||
Text(
|
Text(
|
||||||
text = error ?: "Unknown error",
|
text = error ?: stringResource(Res.string.error_generic),
|
||||||
color = MaterialTheme.colorScheme.error,
|
color = MaterialTheme.colorScheme.error,
|
||||||
modifier = Modifier.padding(AppSpacing.lg)
|
modifier = Modifier.padding(AppSpacing.lg)
|
||||||
)
|
)
|
||||||
@@ -117,7 +119,7 @@ fun ManageUsersDialog(
|
|||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.padding(AppSpacing.lg)) {
|
Column(modifier = Modifier.padding(AppSpacing.lg)) {
|
||||||
Text(
|
Text(
|
||||||
text = "Easy Share",
|
text = stringResource(Res.string.manage_users_easy_share),
|
||||||
style = MaterialTheme.typography.titleSmall,
|
style = MaterialTheme.typography.titleSmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
@@ -127,13 +129,13 @@ fun ManageUsersDialog(
|
|||||||
onClick = { onSharePackage() },
|
onClick = { onSharePackage() },
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.Share, "Share", modifier = Modifier.size(18.dp))
|
Icon(Icons.Default.Share, stringResource(Res.string.common_share), modifier = Modifier.size(18.dp))
|
||||||
Spacer(modifier = Modifier.width(AppSpacing.sm))
|
Spacer(modifier = Modifier.width(AppSpacing.sm))
|
||||||
Text("Send Invite Link")
|
Text(stringResource(Res.string.manage_users_send_invite))
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "Send a .honeydue file via Messages, Email, or share. They just tap to join.",
|
text = stringResource(Res.string.manage_users_easy_share_desc),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
modifier = Modifier.padding(top = AppSpacing.sm)
|
modifier = Modifier.padding(top = AppSpacing.sm)
|
||||||
@@ -148,7 +150,7 @@ fun ManageUsersDialog(
|
|||||||
) {
|
) {
|
||||||
HorizontalDivider(modifier = Modifier.weight(1f))
|
HorizontalDivider(modifier = Modifier.weight(1f))
|
||||||
Text(
|
Text(
|
||||||
text = "or",
|
text = stringResource(Res.string.manage_users_or),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
modifier = Modifier.padding(horizontal = AppSpacing.lg)
|
modifier = Modifier.padding(horizontal = AppSpacing.lg)
|
||||||
@@ -165,7 +167,7 @@ fun ManageUsersDialog(
|
|||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.padding(AppSpacing.lg)) {
|
Column(modifier = Modifier.padding(AppSpacing.lg)) {
|
||||||
Text(
|
Text(
|
||||||
text = "Share Code",
|
text = stringResource(Res.string.manage_users_share_code),
|
||||||
style = MaterialTheme.typography.titleSmall,
|
style = MaterialTheme.typography.titleSmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
@@ -192,13 +194,13 @@ fun ManageUsersDialog(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.ContentCopy,
|
Icons.Default.ContentCopy,
|
||||||
contentDescription = "Copy code",
|
contentDescription = stringResource(Res.string.manage_users_copy_code),
|
||||||
tint = MaterialTheme.colorScheme.primary
|
tint = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Text(
|
Text(
|
||||||
text = "No active code",
|
text = stringResource(Res.string.manage_users_no_code),
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(
|
style = MaterialTheme.typography.bodyMedium.copy(
|
||||||
fontStyle = FontStyle.Italic
|
fontStyle = FontStyle.Italic
|
||||||
),
|
),
|
||||||
@@ -235,15 +237,15 @@ fun ManageUsersDialog(
|
|||||||
strokeWidth = 2.dp
|
strokeWidth = 2.dp
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Icon(Icons.Default.Refresh, "Generate", modifier = Modifier.size(18.dp))
|
Icon(Icons.Default.Refresh, stringResource(Res.string.manage_users_generate), modifier = Modifier.size(18.dp))
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.width(AppSpacing.sm))
|
Spacer(modifier = Modifier.width(AppSpacing.sm))
|
||||||
Text(if (shareCode != null) "Generate New Code" else "Generate Code")
|
Text(if (shareCode != null) stringResource(Res.string.manage_users_generate_new) else stringResource(Res.string.manage_users_generate))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shareCode != null) {
|
if (shareCode != null) {
|
||||||
Text(
|
Text(
|
||||||
text = "Share this 6-character code. They can enter it in the app to join.",
|
text = stringResource(Res.string.manage_users_code_desc),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
modifier = Modifier.padding(top = AppSpacing.sm)
|
modifier = Modifier.padding(top = AppSpacing.sm)
|
||||||
@@ -255,7 +257,7 @@ fun ManageUsersDialog(
|
|||||||
|
|
||||||
// Users list
|
// Users list
|
||||||
Text(
|
Text(
|
||||||
text = "Users (${users.size})",
|
text = stringResource(Res.string.manage_users_users_count, users.size),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
modifier = Modifier.padding(bottom = AppSpacing.sm)
|
modifier = Modifier.padding(bottom = AppSpacing.sm)
|
||||||
)
|
)
|
||||||
@@ -290,7 +292,7 @@ fun ManageUsersDialog(
|
|||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = onDismiss) {
|
TextButton(onClick = onDismiss) {
|
||||||
Text("Close")
|
Text(stringResource(Res.string.common_close))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -324,7 +326,7 @@ private fun UserListItem(
|
|||||||
shape = MaterialTheme.shapes.small
|
shape = MaterialTheme.shapes.small
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "Owner",
|
text = stringResource(Res.string.manage_users_owner_badge),
|
||||||
style = MaterialTheme.typography.labelSmall,
|
style = MaterialTheme.typography.labelSmall,
|
||||||
color = MaterialTheme.colorScheme.onPrimaryContainer,
|
color = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||||
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp)
|
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp)
|
||||||
@@ -355,7 +357,7 @@ private fun UserListItem(
|
|||||||
IconButton(onClick = onRemove) {
|
IconButton(onClick = onRemove) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Delete,
|
Icons.Default.Delete,
|
||||||
contentDescription = "Remove user",
|
contentDescription = stringResource(Res.string.manage_users_remove_user),
|
||||||
tint = MaterialTheme.colorScheme.error
|
tint = MaterialTheme.colorScheme.error
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import coil3.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
import com.tt.honeyDue.ui.theme.AppSpacing
|
import com.tt.honeyDue.ui.theme.AppSpacing
|
||||||
|
import honeydue.composeapp.generated.resources.*
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Full-screen photo viewer with swipeable gallery.
|
* Full-screen photo viewer with swipeable gallery.
|
||||||
@@ -87,7 +89,7 @@ fun PhotoViewerScreen(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Close,
|
Icons.Default.Close,
|
||||||
contentDescription = "Close",
|
contentDescription = stringResource(Res.string.common_close),
|
||||||
tint = Color.White
|
tint = Color.White
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -122,11 +124,11 @@ fun PhotoViewerScreen(
|
|||||||
val isSelected = pagerState.currentPage == index
|
val isSelected = pagerState.currentPage == index
|
||||||
val size by animateFloatAsState(
|
val size by animateFloatAsState(
|
||||||
targetValue = if (isSelected) 10f else 6f,
|
targetValue = if (isSelected) 10f else 6f,
|
||||||
label = "dotSize"
|
label = "dotSize" // i18n-ignore: animation label, non-UI
|
||||||
)
|
)
|
||||||
val alpha by animateFloatAsState(
|
val alpha by animateFloatAsState(
|
||||||
targetValue = if (isSelected) 1f else 0.5f,
|
targetValue = if (isSelected) 1f else 0.5f,
|
||||||
label = "dotAlpha"
|
label = "dotAlpha" // i18n-ignore: animation label, non-UI
|
||||||
)
|
)
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
@@ -187,7 +189,7 @@ private fun ZoomableImage(
|
|||||||
) {
|
) {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = imageUrl,
|
model = imageUrl,
|
||||||
contentDescription = "Photo",
|
contentDescription = stringResource(Res.string.common_photo),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.graphicsLayer(
|
.graphicsLayer(
|
||||||
|
|||||||
+2
-2
@@ -145,7 +145,7 @@ fun ResidenceImportSuccessDialog(
|
|||||||
icon = {
|
icon = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.CheckCircle,
|
imageVector = Icons.Default.CheckCircle,
|
||||||
contentDescription = "Success",
|
contentDescription = stringResource(Res.string.common_success),
|
||||||
modifier = Modifier.size(48.dp),
|
modifier = Modifier.size(48.dp),
|
||||||
tint = MaterialTheme.colorScheme.primary
|
tint = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
@@ -191,7 +191,7 @@ fun ResidenceImportErrorDialog(
|
|||||||
icon = {
|
icon = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Error,
|
imageVector = Icons.Default.Error,
|
||||||
contentDescription = "Error",
|
contentDescription = stringResource(Res.string.common_error),
|
||||||
modifier = Modifier.size(48.dp),
|
modifier = Modifier.size(48.dp),
|
||||||
tint = MaterialTheme.colorScheme.error
|
tint = MaterialTheme.colorScheme.error
|
||||||
)
|
)
|
||||||
|
|||||||
+1
-1
@@ -146,7 +146,7 @@ internal fun getCategoryColor(category: String): androidx.compose.ui.graphics.Co
|
|||||||
"safety", "electrical" -> MaterialTheme.colorScheme.error
|
"safety", "electrical" -> MaterialTheme.colorScheme.error
|
||||||
"hvac" -> MaterialTheme.colorScheme.primary
|
"hvac" -> MaterialTheme.colorScheme.primary
|
||||||
"appliances" -> MaterialTheme.colorScheme.tertiary
|
"appliances" -> MaterialTheme.colorScheme.tertiary
|
||||||
"exterior", "lawn & garden" -> androidx.compose.ui.graphics.Color(0xFF34C759)
|
"exterior", "lawn & garden" -> androidx.compose.ui.graphics.Color(0xFF34C759) // i18n-ignore: category key color lookup, non-UI
|
||||||
"interior" -> androidx.compose.ui.graphics.Color(0xFFAF52DE)
|
"interior" -> androidx.compose.ui.graphics.Color(0xFFAF52DE)
|
||||||
"general", "seasonal" -> androidx.compose.ui.graphics.Color(0xFFFF9500)
|
"general", "seasonal" -> androidx.compose.ui.graphics.Color(0xFFFF9500)
|
||||||
else -> MaterialTheme.colorScheme.primary
|
else -> MaterialTheme.colorScheme.primary
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ fun AuthHeaderPreview() {
|
|||||||
AuthHeader(
|
AuthHeader(
|
||||||
icon = Icons.Default.Home,
|
icon = Icons.Default.Home,
|
||||||
title = "honeyDue",
|
title = "honeyDue",
|
||||||
subtitle = "Manage your properties with ease",
|
subtitle = "Manage your properties with ease", // i18n-ignore: Compose @Preview sample data, not shipped UI
|
||||||
modifier = Modifier.padding(32.dp)
|
modifier = Modifier.padding(32.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-1
@@ -14,6 +14,10 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import honeydue.composeapp.generated.resources.Res
|
||||||
|
import honeydue.composeapp.generated.resources.auth_requirement_met
|
||||||
|
import honeydue.composeapp.generated.resources.auth_requirement_not_met
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RequirementItem(text: String, satisfied: Boolean) {
|
fun RequirementItem(text: String, satisfied: Boolean) {
|
||||||
@@ -22,7 +26,7 @@ fun RequirementItem(text: String, satisfied: Boolean) {
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
if (satisfied) Icons.Default.CheckCircle else Icons.Default.Circle,
|
if (satisfied) Icons.Default.CheckCircle else Icons.Default.Circle,
|
||||||
contentDescription = if (satisfied) "Requirement met" else "Requirement not met",
|
contentDescription = if (satisfied) stringResource(Res.string.auth_requirement_met) else stringResource(Res.string.auth_requirement_not_met),
|
||||||
tint = if (satisfied) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant,
|
tint = if (satisfied) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
modifier = Modifier.size(16.dp)
|
modifier = Modifier.size(16.dp)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ fun ErrorCard(
|
|||||||
fun ErrorCardPreview() {
|
fun ErrorCardPreview() {
|
||||||
MaterialTheme {
|
MaterialTheme {
|
||||||
ErrorCard(
|
ErrorCard(
|
||||||
message = "Invalid username or password. Please try again.",
|
message = "Invalid username or password. Please try again.", // i18n-ignore: Compose @Preview sample data, not shipped UI
|
||||||
modifier = Modifier.padding(16.dp)
|
modifier = Modifier.padding(16.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,11 +59,11 @@ fun InfoCardPreview() {
|
|||||||
MaterialTheme {
|
MaterialTheme {
|
||||||
InfoCard(
|
InfoCard(
|
||||||
icon = Icons.Default.Info,
|
icon = Icons.Default.Info,
|
||||||
title = "Sample Information"
|
title = "Sample Information" // i18n-ignore: Compose @Preview sample data, not shipped UI
|
||||||
) {
|
) {
|
||||||
Text("This is sample content")
|
Text("This is sample content") // i18n-ignore: Compose @Preview sample data, not shipped UI
|
||||||
Text("Line 2 of content")
|
Text("Line 2 of content") // i18n-ignore: Compose @Preview sample data, not shipped UI
|
||||||
Text("Line 3 of content")
|
Text("Line 3 of content") // i18n-ignore: Compose @Preview sample data, not shipped UI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-5
@@ -11,6 +11,8 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
|||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.tt.honeyDue.ui.theme.AppSpacing
|
import com.tt.honeyDue.ui.theme.AppSpacing
|
||||||
|
import honeydue.composeapp.generated.resources.*
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* StandardErrorState - Inline error state with retry button
|
* StandardErrorState - Inline error state with retry button
|
||||||
@@ -34,9 +36,9 @@ fun StandardErrorState(
|
|||||||
message: String,
|
message: String,
|
||||||
onRetry: () -> Unit,
|
onRetry: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
title: String = "Something went wrong",
|
title: String = stringResource(Res.string.error_something_wrong),
|
||||||
icon: ImageVector = Icons.Default.ErrorOutline,
|
icon: ImageVector = Icons.Default.ErrorOutline,
|
||||||
retryLabel: String = "Retry"
|
retryLabel: String = stringResource(Res.string.common_retry)
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
@@ -47,7 +49,7 @@ fun StandardErrorState(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = icon,
|
imageVector = icon,
|
||||||
contentDescription = "Error",
|
contentDescription = stringResource(Res.string.common_error),
|
||||||
modifier = Modifier.size(48.dp),
|
modifier = Modifier.size(48.dp),
|
||||||
tint = MaterialTheme.colorScheme.error
|
tint = MaterialTheme.colorScheme.error
|
||||||
)
|
)
|
||||||
@@ -79,7 +81,7 @@ fun CompactErrorState(
|
|||||||
message: String,
|
message: String,
|
||||||
onRetry: () -> Unit,
|
onRetry: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
retryLabel: String = "Retry"
|
retryLabel: String = stringResource(Res.string.common_retry)
|
||||||
) {
|
) {
|
||||||
Card(
|
Card(
|
||||||
colors = CardDefaults.cardColors(
|
colors = CardDefaults.cardColors(
|
||||||
@@ -99,7 +101,7 @@ fun CompactErrorState(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.ErrorOutline,
|
imageVector = Icons.Default.ErrorOutline,
|
||||||
contentDescription = "Error",
|
contentDescription = stringResource(Res.string.common_error),
|
||||||
tint = MaterialTheme.colorScheme.onErrorContainer
|
tint = MaterialTheme.colorScheme.onErrorContainer
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ fun StatItemPreview() {
|
|||||||
StatItem(
|
StatItem(
|
||||||
icon = Icons.Default.Home,
|
icon = Icons.Default.Home,
|
||||||
value = "5",
|
value = "5",
|
||||||
label = "Properties"
|
label = "Properties" // i18n-ignore: Compose @Preview sample data, not shipped UI
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -61,7 +61,7 @@ fun DeleteAccountDialog(
|
|||||||
// Warning Icon
|
// Warning Icon
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Warning,
|
imageVector = Icons.Default.Warning,
|
||||||
contentDescription = "Warning",
|
contentDescription = stringResource(Res.string.common_warning),
|
||||||
tint = MaterialTheme.colorScheme.error,
|
tint = MaterialTheme.colorScheme.error,
|
||||||
modifier = Modifier.size(48.dp)
|
modifier = Modifier.size(48.dp)
|
||||||
)
|
)
|
||||||
|
|||||||
+11
-9
@@ -21,6 +21,8 @@ import com.tt.honeyDue.models.DocumentType
|
|||||||
import com.tt.honeyDue.testing.AccessibilityIds
|
import com.tt.honeyDue.testing.AccessibilityIds
|
||||||
import com.tt.honeyDue.ui.theme.AppRadius
|
import com.tt.honeyDue.ui.theme.AppRadius
|
||||||
import com.tt.honeyDue.ui.theme.AppSpacing
|
import com.tt.honeyDue.ui.theme.AppSpacing
|
||||||
|
import honeydue.composeapp.generated.resources.*
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DocumentCard(document: Document, isWarrantyCard: Boolean = false, onClick: () -> Unit) {
|
fun DocumentCard(document: Document, isWarrantyCard: Boolean = false, onClick: () -> Unit) {
|
||||||
@@ -83,10 +85,10 @@ private fun WarrantyCardContent(document: Document, onClick: () -> Unit) {
|
|||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
when {
|
when {
|
||||||
!document.isActive -> "Inactive"
|
!document.isActive -> stringResource(Res.string.documents_inactive)
|
||||||
daysUntilExpiration < 0 -> "Expired"
|
daysUntilExpiration < 0 -> stringResource(Res.string.documents_expired)
|
||||||
daysUntilExpiration < 30 -> "Expiring soon"
|
daysUntilExpiration < 30 -> stringResource(Res.string.documents_expiring_soon)
|
||||||
else -> "Active"
|
else -> stringResource(Res.string.documents_active)
|
||||||
},
|
},
|
||||||
style = MaterialTheme.typography.labelSmall,
|
style = MaterialTheme.typography.labelSmall,
|
||||||
color = statusColor,
|
color = statusColor,
|
||||||
@@ -102,19 +104,19 @@ private fun WarrantyCardContent(document: Document, onClick: () -> Unit) {
|
|||||||
horizontalArrangement = Arrangement.SpaceBetween
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
Text("Provider", style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
Text(stringResource(Res.string.documents_provider), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||||
Text(document.provider ?: "N/A", style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Medium)
|
Text(document.provider ?: stringResource(Res.string.tasks_card_not_available), style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Medium)
|
||||||
}
|
}
|
||||||
Column(horizontalAlignment = Alignment.End) {
|
Column(horizontalAlignment = Alignment.End) {
|
||||||
Text("Expires", style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
Text(stringResource(Res.string.documents_expires_label), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||||
Text(document.endDate ?: "N/A", style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Medium)
|
Text(document.endDate ?: stringResource(Res.string.tasks_card_not_available), style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Medium)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.isActive && daysUntilExpiration >= 0) {
|
if (document.isActive && daysUntilExpiration >= 0) {
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.sm))
|
Spacer(modifier = Modifier.height(AppSpacing.sm))
|
||||||
Text(
|
Text(
|
||||||
"$daysUntilExpiration days remaining",
|
stringResource(Res.string.documents_days_remaining_count, daysUntilExpiration),
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
color = statusColor
|
color = statusColor
|
||||||
)
|
)
|
||||||
|
|||||||
+4
-2
@@ -10,6 +10,8 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import honeydue.composeapp.generated.resources.*
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun EmptyState(
|
fun EmptyState(
|
||||||
@@ -35,12 +37,12 @@ fun ErrorState(message: String, onRetry: () -> Unit) {
|
|||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.Center
|
verticalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.Error, contentDescription = "Error", modifier = Modifier.size(64.dp) , tint = MaterialTheme.colorScheme.error)
|
Icon(Icons.Default.Error, contentDescription = stringResource(Res.string.common_error), modifier = Modifier.size(64.dp) , tint = MaterialTheme.colorScheme.error)
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
Text(message, style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
Text(message, style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
OutlinedButton(onClick = onRetry) {
|
OutlinedButton(onClick = onRetry) {
|
||||||
Text("Retry")
|
Text(stringResource(Res.string.common_retry))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-1
@@ -19,6 +19,10 @@ import com.tt.honeyDue.network.ApiResult
|
|||||||
import com.tt.honeyDue.testing.AccessibilityIds
|
import com.tt.honeyDue.testing.AccessibilityIds
|
||||||
import com.tt.honeyDue.ui.subscription.UpgradeFeatureScreen
|
import com.tt.honeyDue.ui.subscription.UpgradeFeatureScreen
|
||||||
import com.tt.honeyDue.utils.SubscriptionHelper
|
import com.tt.honeyDue.utils.SubscriptionHelper
|
||||||
|
import honeydue.composeapp.generated.resources.Res
|
||||||
|
import honeydue.composeapp.generated.resources.documents_empty_no_warranties
|
||||||
|
import honeydue.composeapp.generated.resources.documents_empty_no_documents
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -65,7 +69,7 @@ fun DocumentsTabContent(
|
|||||||
EmptyState(
|
EmptyState(
|
||||||
modifier = Modifier.testTag(AccessibilityIds.Document.emptyStateView),
|
modifier = Modifier.testTag(AccessibilityIds.Document.emptyStateView),
|
||||||
icon = if (isWarrantyTab) Icons.Default.ReceiptLong else Icons.Default.Description,
|
icon = if (isWarrantyTab) Icons.Default.ReceiptLong else Icons.Default.Description,
|
||||||
message = if (isWarrantyTab) "No warranties found" else "No documents found"
|
message = if (isWarrantyTab) stringResource(Res.string.documents_empty_no_warranties) else stringResource(Res.string.documents_empty_no_documents)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+2
-2
@@ -48,8 +48,8 @@ fun DetailRowPreview() {
|
|||||||
MaterialTheme {
|
MaterialTheme {
|
||||||
DetailRow(
|
DetailRow(
|
||||||
icon = Icons.Default.SquareFoot,
|
icon = Icons.Default.SquareFoot,
|
||||||
label = "Square Footage",
|
label = "Square Footage", // i18n-ignore: Compose @Preview sample data, not shipped UI
|
||||||
value = "1800 sq ft"
|
value = "1800 sq ft" // i18n-ignore: Compose @Preview sample data, not shipped UI
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -48,7 +48,7 @@ fun PropertyDetailItemPreview() {
|
|||||||
PropertyDetailItem(
|
PropertyDetailItem(
|
||||||
icon = Icons.Default.Bed,
|
icon = Icons.Default.Bed,
|
||||||
value = "3",
|
value = "3",
|
||||||
label = "Bedrooms"
|
label = "Bedrooms" // i18n-ignore: Compose @Preview sample data, not shipped UI
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -51,7 +51,7 @@ fun TaskStatChipPreview() {
|
|||||||
TaskStatChip(
|
TaskStatChip(
|
||||||
icon = Icons.Default.CheckCircle,
|
icon = Icons.Default.CheckCircle,
|
||||||
value = "12",
|
value = "12",
|
||||||
label = "Completed",
|
label = "Completed", // i18n-ignore: Compose @Preview sample data, not shipped UI
|
||||||
color = MaterialTheme.colorScheme.tertiary
|
color = MaterialTheme.colorScheme.tertiary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-10
@@ -17,6 +17,8 @@ import com.tt.honeyDue.models.TaskCompletion
|
|||||||
import com.tt.honeyDue.network.ApiResult
|
import com.tt.honeyDue.network.ApiResult
|
||||||
import com.tt.honeyDue.network.APILayer
|
import com.tt.honeyDue.network.APILayer
|
||||||
import com.tt.honeyDue.util.DateUtils
|
import com.tt.honeyDue.util.DateUtils
|
||||||
|
import honeydue.composeapp.generated.resources.*
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,7 +68,7 @@ fun CompletionHistorySheet(
|
|||||||
) {
|
) {
|
||||||
// Header
|
// Header
|
||||||
Text(
|
Text(
|
||||||
text = "Completion History",
|
text = stringResource(Res.string.completion_history_title),
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
modifier = Modifier.padding(bottom = 8.dp)
|
modifier = Modifier.padding(bottom = 8.dp)
|
||||||
@@ -98,7 +100,8 @@ fun CompletionHistorySheet(
|
|||||||
shape = RoundedCornerShape(12.dp)
|
shape = RoundedCornerShape(12.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "${completions.size} ${if (completions.size == 1) "completion" else "completions"}",
|
text = if (completions.size == 1) stringResource(Res.string.completion_history_count_one, completions.size)
|
||||||
|
else stringResource(Res.string.completion_history_count_other, completions.size),
|
||||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp),
|
modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp),
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
color = MaterialTheme.colorScheme.onPrimaryContainer
|
color = MaterialTheme.colorScheme.onPrimaryContainer
|
||||||
@@ -125,7 +128,7 @@ fun CompletionHistorySheet(
|
|||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
Text(
|
Text(
|
||||||
text = "Loading completions...",
|
text = stringResource(Res.string.completion_history_loading),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
@@ -148,7 +151,7 @@ fun CompletionHistorySheet(
|
|||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
Text(
|
Text(
|
||||||
text = "Failed to load completions",
|
text = stringResource(Res.string.completion_history_load_failed),
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium,
|
||||||
color = MaterialTheme.colorScheme.onSurface
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
@@ -182,7 +185,7 @@ fun CompletionHistorySheet(
|
|||||||
) {
|
) {
|
||||||
Icon(Icons.Default.Refresh, contentDescription = null) // decorative
|
Icon(Icons.Default.Refresh, contentDescription = null) // decorative
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
Text("Retry")
|
Text(stringResource(Res.string.common_retry))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,13 +206,13 @@ fun CompletionHistorySheet(
|
|||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
Text(
|
Text(
|
||||||
text = "No Completions Yet",
|
text = stringResource(Res.string.completion_history_empty_title),
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium,
|
||||||
color = MaterialTheme.colorScheme.onSurface
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = "This task has not been completed.",
|
text = stringResource(Res.string.completion_history_empty_message),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
@@ -275,7 +278,7 @@ private fun CompletionHistoryCard(completion: TaskCompletionResponse) {
|
|||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
Text(
|
Text(
|
||||||
text = "Completed by ${user.displayName}",
|
text = stringResource(Res.string.completion_history_completed_by, user.displayName),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
@@ -308,7 +311,7 @@ private fun CompletionHistoryCard(completion: TaskCompletionResponse) {
|
|||||||
if (completion.notes.isNotEmpty()) {
|
if (completion.notes.isNotEmpty()) {
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
Text(
|
Text(
|
||||||
text = "Notes",
|
text = stringResource(Res.string.completions_notes_label),
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
@@ -360,7 +363,8 @@ private fun CompletionHistoryCard(completion: TaskCompletionResponse) {
|
|||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = if (completion.images.size == 1) "View Photo" else "View Photos (${completion.images.size})",
|
text = if (completion.images.size == 1) stringResource(Res.string.completion_history_view_photo)
|
||||||
|
else stringResource(Res.string.tasks_card_view_photos, completion.images.size),
|
||||||
style = MaterialTheme.typography.labelLarge,
|
style = MaterialTheme.typography.labelLarge,
|
||||||
fontWeight = FontWeight.SemiBold
|
fontWeight = FontWeight.SemiBold
|
||||||
)
|
)
|
||||||
|
|||||||
+6
-4
@@ -28,6 +28,8 @@ import com.tt.honeyDue.network.ApiClient
|
|||||||
import com.tt.honeyDue.ui.components.AuthenticatedImage
|
import com.tt.honeyDue.ui.components.AuthenticatedImage
|
||||||
import com.tt.honeyDue.ui.theme.AppRadius
|
import com.tt.honeyDue.ui.theme.AppRadius
|
||||||
import com.tt.honeyDue.ui.theme.AppSpacing
|
import com.tt.honeyDue.ui.theme.AppSpacing
|
||||||
|
import honeydue.composeapp.generated.resources.*
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PhotoViewerDialog(
|
fun PhotoViewerDialog(
|
||||||
@@ -69,7 +71,7 @@ fun PhotoViewerDialog(
|
|||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = if (selectedImage != null) "Photo" else "Completion Photos",
|
text = if (selectedImage != null) stringResource(Res.string.common_photo) else stringResource(Res.string.photos_completion_title),
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
@@ -82,7 +84,7 @@ fun PhotoViewerDialog(
|
|||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Close,
|
Icons.Default.Close,
|
||||||
contentDescription = "Close"
|
contentDescription = stringResource(Res.string.common_close)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,7 +103,7 @@ fun PhotoViewerDialog(
|
|||||||
) {
|
) {
|
||||||
AuthenticatedImage(
|
AuthenticatedImage(
|
||||||
mediaUrl = selectedImage!!.mediaUrl,
|
mediaUrl = selectedImage!!.mediaUrl,
|
||||||
contentDescription = selectedImage!!.caption ?: "Task completion photo",
|
contentDescription = selectedImage!!.caption ?: stringResource(Res.string.photos_task_completion_cd),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.weight(1f),
|
.weight(1f),
|
||||||
@@ -143,7 +145,7 @@ fun PhotoViewerDialog(
|
|||||||
Column {
|
Column {
|
||||||
AuthenticatedImage(
|
AuthenticatedImage(
|
||||||
mediaUrl = image.mediaUrl,
|
mediaUrl = image.mediaUrl,
|
||||||
contentDescription = image.caption ?: "Task completion photo",
|
contentDescription = image.caption ?: stringResource(Res.string.photos_task_completion_cd),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.aspectRatio(1f),
|
.aspectRatio(1f),
|
||||||
|
|||||||
@@ -660,8 +660,8 @@ fun TaskCardPreview() {
|
|||||||
id = 1,
|
id = 1,
|
||||||
residenceId = 1,
|
residenceId = 1,
|
||||||
createdById = 1,
|
createdById = 1,
|
||||||
title = "Clean Gutters",
|
title = "Clean Gutters", // i18n-ignore: Compose @Preview sample data, not shipped UI
|
||||||
description = "Remove all debris from gutters and downspouts",
|
description = "Remove all debris from gutters and downspouts", // i18n-ignore: Compose @Preview sample data, not shipped UI
|
||||||
category = TaskCategory(id = 1, name = "maintenance"),
|
category = TaskCategory(id = 1, name = "maintenance"),
|
||||||
priority = TaskPriority(id = 2, name = "medium"),
|
priority = TaskPriority(id = 2, name = "medium"),
|
||||||
frequency = TaskFrequency(
|
frequency = TaskFrequency(
|
||||||
@@ -670,8 +670,8 @@ fun TaskCardPreview() {
|
|||||||
inProgress = false,
|
inProgress = false,
|
||||||
dueDate = "2024-12-15",
|
dueDate = "2024-12-15",
|
||||||
estimatedCost = 150.00,
|
estimatedCost = 150.00,
|
||||||
createdAt = "2024-01-01T00:00:00Z",
|
createdAt = "2024-01-01T00:00:00Z", // i18n-ignore: Compose @Preview sample data, not shipped UI
|
||||||
updatedAt = "2024-01-01T00:00:00Z",
|
updatedAt = "2024-01-01T00:00:00Z", // i18n-ignore: Compose @Preview sample data, not shipped UI
|
||||||
completions = emptyList()
|
completions = emptyList()
|
||||||
),
|
),
|
||||||
onCompleteClick = {},
|
onCompleteClick = {},
|
||||||
|
|||||||
+9
-7
@@ -26,6 +26,8 @@ import com.tt.honeyDue.models.TaskDetail
|
|||||||
import com.tt.honeyDue.testing.AccessibilityIds
|
import com.tt.honeyDue.testing.AccessibilityIds
|
||||||
import com.tt.honeyDue.ui.theme.AppRadius
|
import com.tt.honeyDue.ui.theme.AppRadius
|
||||||
import com.tt.honeyDue.ui.theme.AppSpacing
|
import com.tt.honeyDue.ui.theme.AppSpacing
|
||||||
|
import honeydue.composeapp.generated.resources.*
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -53,7 +55,7 @@ fun TaskKanbanView(
|
|||||||
) { page ->
|
) { page ->
|
||||||
when (page) {
|
when (page) {
|
||||||
0 -> TaskColumn(
|
0 -> TaskColumn(
|
||||||
title = "Upcoming",
|
title = stringResource(Res.string.tasks_column_upcoming),
|
||||||
icon = Icons.Default.CalendarToday,
|
icon = Icons.Default.CalendarToday,
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
count = upcomingTasks.size,
|
count = upcomingTasks.size,
|
||||||
@@ -67,7 +69,7 @@ fun TaskKanbanView(
|
|||||||
onUnarchiveTask = null
|
onUnarchiveTask = null
|
||||||
)
|
)
|
||||||
1 -> TaskColumn(
|
1 -> TaskColumn(
|
||||||
title = "In Progress",
|
title = stringResource(Res.string.tasks_column_in_progress),
|
||||||
icon = Icons.Default.PlayCircle,
|
icon = Icons.Default.PlayCircle,
|
||||||
color = MaterialTheme.colorScheme.tertiary,
|
color = MaterialTheme.colorScheme.tertiary,
|
||||||
count = inProgressTasks.size,
|
count = inProgressTasks.size,
|
||||||
@@ -81,7 +83,7 @@ fun TaskKanbanView(
|
|||||||
onUnarchiveTask = null
|
onUnarchiveTask = null
|
||||||
)
|
)
|
||||||
2 -> TaskColumn(
|
2 -> TaskColumn(
|
||||||
title = "Done",
|
title = stringResource(Res.string.tasks_column_done),
|
||||||
icon = Icons.Default.CheckCircle,
|
icon = Icons.Default.CheckCircle,
|
||||||
color = MaterialTheme.colorScheme.secondary,
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
count = doneTasks.size,
|
count = doneTasks.size,
|
||||||
@@ -95,7 +97,7 @@ fun TaskKanbanView(
|
|||||||
onUnarchiveTask = null
|
onUnarchiveTask = null
|
||||||
)
|
)
|
||||||
3 -> TaskColumn(
|
3 -> TaskColumn(
|
||||||
title = "Archived",
|
title = stringResource(Res.string.tasks_column_archived),
|
||||||
icon = Icons.Default.Archive,
|
icon = Icons.Default.Archive,
|
||||||
color = MaterialTheme.colorScheme.outline,
|
color = MaterialTheme.colorScheme.outline,
|
||||||
count = archivedTasks.size,
|
count = archivedTasks.size,
|
||||||
@@ -193,7 +195,7 @@ private fun TaskColumn(
|
|||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.sm))
|
Spacer(modifier = Modifier.height(AppSpacing.sm))
|
||||||
Text(
|
Text(
|
||||||
text = "No tasks",
|
text = stringResource(Res.string.tasks_column_empty),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
@@ -314,7 +316,7 @@ private fun DynamicTaskColumn(
|
|||||||
bottomPadding: androidx.compose.ui.unit.Dp = 0.dp
|
bottomPadding: androidx.compose.ui.unit.Dp = 0.dp
|
||||||
) {
|
) {
|
||||||
// Get icon from API response, with fallback
|
// Get icon from API response, with fallback
|
||||||
val columnIcon = getIconFromName(column.icons["android"] ?: "List")
|
val columnIcon = getIconFromName(column.icons["android"] ?: "List") // i18n-ignore: icon name identifier, non-UI
|
||||||
|
|
||||||
val columnColor = hexToColor(column.color)
|
val columnColor = hexToColor(column.color)
|
||||||
|
|
||||||
@@ -378,7 +380,7 @@ private fun DynamicTaskColumn(
|
|||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(AppSpacing.sm))
|
Spacer(modifier = Modifier.height(AppSpacing.sm))
|
||||||
Text(
|
Text(
|
||||||
text = "No tasks",
|
text = stringResource(Res.string.tasks_column_empty),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -157,9 +157,9 @@ class BlobShape(
|
|||||||
object Close : PathOp()
|
object Close : PathOp()
|
||||||
|
|
||||||
fun toSerialized(): String = when (this) {
|
fun toSerialized(): String = when (this) {
|
||||||
is MoveTo -> "M ${fmt(p.x)},${fmt(p.y)}"
|
is MoveTo -> "M ${fmt(p.x)},${fmt(p.y)}" // i18n-ignore: SVG path command, non-UI geometry
|
||||||
is LineTo -> "L ${fmt(p.x)},${fmt(p.y)}"
|
is LineTo -> "L ${fmt(p.x)},${fmt(p.y)}" // i18n-ignore: SVG path command, non-UI geometry
|
||||||
is CubicTo -> "C ${fmt(c1.x)},${fmt(c1.y)} ${fmt(c2.x)},${fmt(c2.y)} ${fmt(end.x)},${fmt(end.y)}"
|
is CubicTo -> "C ${fmt(c1.x)},${fmt(c1.y)} ${fmt(c2.x)},${fmt(c2.y)} ${fmt(end.x)},${fmt(end.y)}" // i18n-ignore: SVG path command, non-UI geometry
|
||||||
Close -> "Z"
|
Close -> "Z"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ import com.tt.honeyDue.viewmodel.TaskViewModel
|
|||||||
import com.tt.honeyDue.models.TaskDetail
|
import com.tt.honeyDue.models.TaskDetail
|
||||||
import com.tt.honeyDue.network.ApiResult
|
import com.tt.honeyDue.network.ApiResult
|
||||||
import com.tt.honeyDue.ui.theme.*
|
import com.tt.honeyDue.ui.theme.*
|
||||||
|
import honeydue.composeapp.generated.resources.*
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -102,7 +104,7 @@ fun AllTasksScreen(
|
|||||||
// Handle errors for task creation
|
// Handle errors for task creation
|
||||||
createTaskState.HandleErrors(
|
createTaskState.HandleErrors(
|
||||||
onRetry = { /* Retry handled in dialog */ },
|
onRetry = { /* Retry handled in dialog */ },
|
||||||
errorTitle = "Failed to Create Task"
|
errorTitle = stringResource(Res.string.tasks_create_failed_title)
|
||||||
)
|
)
|
||||||
|
|
||||||
LaunchedEffect(createTaskState) {
|
LaunchedEffect(createTaskState) {
|
||||||
@@ -125,7 +127,7 @@ fun AllTasksScreen(
|
|||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(
|
||||||
"All Tasks",
|
stringResource(Res.string.tasks_all_title),
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -136,7 +138,7 @@ fun AllTasksScreen(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Refresh,
|
Icons.Default.Refresh,
|
||||||
contentDescription = "Refresh"
|
contentDescription = stringResource(Res.string.common_refresh)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
IconButton(
|
IconButton(
|
||||||
@@ -147,7 +149,7 @@ fun AllTasksScreen(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Add,
|
Icons.Default.Add,
|
||||||
contentDescription = "Add Task"
|
contentDescription = stringResource(Res.string.tasks_add)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -161,7 +163,7 @@ fun AllTasksScreen(
|
|||||||
state = tasksState,
|
state = tasksState,
|
||||||
onRetry = { viewModel.loadTasks(forceRefresh = true) },
|
onRetry = { viewModel.loadTasks(forceRefresh = true) },
|
||||||
modifier = Modifier.padding(paddingValues),
|
modifier = Modifier.padding(paddingValues),
|
||||||
errorTitle = "Failed to Load Tasks"
|
errorTitle = stringResource(Res.string.tasks_load_failed_title)
|
||||||
) { taskData ->
|
) { taskData ->
|
||||||
val hasNoTasks = taskData.columns.all { it.tasks.isEmpty() }
|
val hasNoTasks = taskData.columns.all { it.tasks.isEmpty() }
|
||||||
|
|
||||||
@@ -184,19 +186,19 @@ fun AllTasksScreen(
|
|||||||
iconColor = MaterialTheme.colorScheme.primary
|
iconColor = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
"No tasks yet",
|
stringResource(Res.string.tasks_empty_title),
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
color = MaterialTheme.colorScheme.textPrimary
|
color = MaterialTheme.colorScheme.textPrimary
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
"Create your first task to get started",
|
stringResource(Res.string.tasks_all_empty_subtitle),
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
color = MaterialTheme.colorScheme.textSecondary
|
color = MaterialTheme.colorScheme.textSecondary
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(OrganicSpacing.compact))
|
Spacer(modifier = Modifier.height(OrganicSpacing.compact))
|
||||||
OrganicPrimaryButton(
|
OrganicPrimaryButton(
|
||||||
text = "Add Task",
|
text = stringResource(Res.string.tasks_add),
|
||||||
onClick = { showNewTaskDialog = true },
|
onClick = { showNewTaskDialog = true },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth(0.7f)
|
.fillMaxWidth(0.7f)
|
||||||
@@ -207,7 +209,7 @@ fun AllTasksScreen(
|
|||||||
if (myResidencesState is ApiResult.Success &&
|
if (myResidencesState is ApiResult.Success &&
|
||||||
(myResidencesState as ApiResult.Success).data.residences.isEmpty()) {
|
(myResidencesState as ApiResult.Success).data.residences.isEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
"Add a property first from the Residences tab",
|
stringResource(Res.string.tasks_add_property_first),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.error
|
color = MaterialTheme.colorScheme.error
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ fun BiometricLockScreen(
|
|||||||
|
|
||||||
val promptTitle = stringResource(Res.string.biometric_prompt_title)
|
val promptTitle = stringResource(Res.string.biometric_prompt_title)
|
||||||
val promptSubtitle = stringResource(Res.string.biometric_prompt_subtitle)
|
val promptSubtitle = stringResource(Res.string.biometric_prompt_subtitle)
|
||||||
|
val incorrectPinMessage = stringResource(Res.string.biometric_incorrect_pin)
|
||||||
|
|
||||||
// Callback that maps platform result back onto the state machine.
|
// Callback that maps platform result back onto the state machine.
|
||||||
fun triggerPrompt() {
|
fun triggerPrompt() {
|
||||||
@@ -134,7 +135,7 @@ fun BiometricLockScreen(
|
|||||||
if (showFallback) {
|
if (showFallback) {
|
||||||
// ----- Fallback PIN UI (after 3 biometric failures) -----
|
// ----- Fallback PIN UI (after 3 biometric failures) -----
|
||||||
Text(
|
Text(
|
||||||
text = "Enter PIN to unlock",
|
text = stringResource(Res.string.biometric_enter_pin),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
@@ -148,7 +149,7 @@ fun BiometricLockScreen(
|
|||||||
pinError = null
|
pinError = null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
label = { Text("4-digit PIN") },
|
label = { Text(stringResource(Res.string.biometric_pin_label)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.NumberPassword),
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.NumberPassword),
|
||||||
isError = pinError != null,
|
isError = pinError != null,
|
||||||
@@ -164,7 +165,7 @@ fun BiometricLockScreen(
|
|||||||
// storage. See BiometricLockScreen.kt:line
|
// storage. See BiometricLockScreen.kt:line
|
||||||
// for the PIN constant below.
|
// for the PIN constant below.
|
||||||
if (!lockState.onPinEntered(pinInput, TODO_FALLBACK_PIN)) {
|
if (!lockState.onPinEntered(pinInput, TODO_FALLBACK_PIN)) {
|
||||||
pinError = "Incorrect PIN"
|
pinError = incorrectPinMessage
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
enabled = pinInput.length == 4,
|
enabled = pinInput.length == 4,
|
||||||
@@ -174,7 +175,7 @@ fun BiometricLockScreen(
|
|||||||
shape = MaterialTheme.shapes.medium,
|
shape = MaterialTheme.shapes.medium,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "Unlock",
|
text = stringResource(Res.string.biometric_unlock),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.SemiBold
|
fontWeight = FontWeight.SemiBold
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ fun CompleteTaskScreen(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = OrganicSpacing.lg)
|
.padding(horizontal = OrganicSpacing.lg)
|
||||||
.clickableWithRipple(onClickLabel = "Select contractor") {
|
.clickableWithRipple(onClickLabel = stringResource(Res.string.completions_select_contractor)) {
|
||||||
showContractorPicker = true
|
showContractorPicker = true
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
@@ -305,7 +305,7 @@ fun CompleteTaskScreen(
|
|||||||
targetValue = if (isSelected) Color(0xFFFFD700)
|
targetValue = if (isSelected) Color(0xFFFFD700)
|
||||||
else MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.3f),
|
else MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.3f),
|
||||||
animationSpec = tween(durationMillis = 150),
|
animationSpec = tween(durationMillis = 150),
|
||||||
label = "starColor"
|
label = "starColor" // i18n-ignore: animation label, non-UI
|
||||||
)
|
)
|
||||||
|
|
||||||
IconButton(
|
IconButton(
|
||||||
@@ -317,7 +317,7 @@ fun CompleteTaskScreen(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = if (isSelected) Icons.Default.Star else Icons.Default.StarOutline,
|
imageVector = if (isSelected) Icons.Default.Star else Icons.Default.StarOutline,
|
||||||
contentDescription = "$star stars",
|
contentDescription = stringResource(Res.string.completions_star_rating_cd, star),
|
||||||
tint = starColor,
|
tint = starColor,
|
||||||
modifier = Modifier.size(40.dp)
|
modifier = Modifier.size(40.dp)
|
||||||
)
|
)
|
||||||
@@ -395,6 +395,8 @@ fun CompleteTaskScreen(
|
|||||||
Spacer(modifier = Modifier.height(OrganicSpacing.xl))
|
Spacer(modifier = Modifier.height(OrganicSpacing.xl))
|
||||||
|
|
||||||
// Complete Button
|
// Complete Button
|
||||||
|
val contractorPrefix = stringResource(Res.string.completions_contractor_prefix)
|
||||||
|
val completedByPrefix = stringResource(Res.string.completions_completed_by_prefix)
|
||||||
OrganicPrimaryButton(
|
OrganicPrimaryButton(
|
||||||
text = stringResource(Res.string.completions_complete_button),
|
text = stringResource(Res.string.completions_complete_button),
|
||||||
onClick = {
|
onClick = {
|
||||||
@@ -402,12 +404,12 @@ fun CompleteTaskScreen(
|
|||||||
isSubmitting = true
|
isSubmitting = true
|
||||||
val notesWithContractor = buildString {
|
val notesWithContractor = buildString {
|
||||||
selectedContractor?.let {
|
selectedContractor?.let {
|
||||||
append("Contractor: ${it.name}")
|
append("$contractorPrefix ${it.name}")
|
||||||
it.company?.let { company -> append(" ($company)") }
|
it.company?.let { company -> append(" ($company)") }
|
||||||
append("\n")
|
append("\n")
|
||||||
}
|
}
|
||||||
if (completedByName.isNotBlank()) {
|
if (completedByName.isNotBlank()) {
|
||||||
append("Completed by: $completedByName\n")
|
append("$completedByPrefix $completedByName\n") // i18n-ignore: localized prefix var + data concatenation, no literal prose
|
||||||
}
|
}
|
||||||
if (notes.isNotBlank()) {
|
if (notes.isNotBlank()) {
|
||||||
append(notes)
|
append(notes)
|
||||||
@@ -527,7 +529,7 @@ private fun ImageThumbnailCard(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.TopEnd)
|
.align(Alignment.TopEnd)
|
||||||
.minTouchTarget()
|
.minTouchTarget()
|
||||||
.clickableWithRipple(onClickLabel = "Remove photo", onClick = onRemove),
|
.clickableWithRipple(onClickLabel = stringResource(Res.string.completions_remove_photo), onClick = onRemove),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
@@ -585,7 +587,7 @@ private fun ContractorPickerSheet(
|
|||||||
if (selectedContractor == null) {
|
if (selectedContractor == null) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Check,
|
Icons.Default.Check,
|
||||||
contentDescription = "Selected",
|
contentDescription = stringResource(Res.string.common_selected),
|
||||||
tint = MaterialTheme.colorScheme.primary
|
tint = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -617,7 +619,7 @@ private fun ContractorPickerSheet(
|
|||||||
if (selectedContractor?.id == contractor.id) {
|
if (selectedContractor?.id == contractor.id) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Check,
|
Icons.Default.Check,
|
||||||
contentDescription = "Selected",
|
contentDescription = stringResource(Res.string.common_selected),
|
||||||
tint = MaterialTheme.colorScheme.primary
|
tint = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-8
@@ -157,6 +157,7 @@ fun ContractorDetailScreen(
|
|||||||
CircularProgressIndicator(color = MaterialTheme.colorScheme.primary)
|
CircularProgressIndicator(color = MaterialTheme.colorScheme.primary)
|
||||||
}
|
}
|
||||||
) { contractor ->
|
) { contractor ->
|
||||||
|
val propertyRefLabel = stringResource(Res.string.contractors_property_ref)
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
contentPadding = PaddingValues(OrganicSpacing.medium),
|
contentPadding = PaddingValues(OrganicSpacing.medium),
|
||||||
@@ -281,7 +282,7 @@ fun ContractorDetailScreen(
|
|||||||
.testTag(AccessibilityIds.Contractor.callButton),
|
.testTag(AccessibilityIds.Contractor.callButton),
|
||||||
onClick = {
|
onClick = {
|
||||||
try {
|
try {
|
||||||
uriHandler.openUri("tel:${phone.replace(" ", "")}")
|
uriHandler.openUri("tel:${phone.replace(" ", "")}") // i18n-ignore: tel: URI, non-UI
|
||||||
} catch (e: Exception) { /* Handle error */ }
|
} catch (e: Exception) { /* Handle error */ }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -297,7 +298,7 @@ fun ContractorDetailScreen(
|
|||||||
.testTag(AccessibilityIds.Contractor.emailButton),
|
.testTag(AccessibilityIds.Contractor.emailButton),
|
||||||
onClick = {
|
onClick = {
|
||||||
try {
|
try {
|
||||||
uriHandler.openUri("mailto:$email")
|
uriHandler.openUri("mailto:$email") // i18n-ignore: mailto: URI, non-UI
|
||||||
} catch (e: Exception) { /* Handle error */ }
|
} catch (e: Exception) { /* Handle error */ }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -332,7 +333,7 @@ fun ContractorDetailScreen(
|
|||||||
contractor.stateProvince,
|
contractor.stateProvince,
|
||||||
contractor.postalCode
|
contractor.postalCode
|
||||||
).joinToString(", ")
|
).joinToString(", ")
|
||||||
uriHandler.openUri("geo:0,0?q=$address")
|
uriHandler.openUri("geo:0,0?q=$address") // i18n-ignore: geo: URI, non-UI
|
||||||
} catch (e: Exception) { /* Handle error */ }
|
} catch (e: Exception) { /* Handle error */ }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -352,7 +353,7 @@ fun ContractorDetailScreen(
|
|||||||
iconTint = MaterialTheme.colorScheme.primary,
|
iconTint = MaterialTheme.colorScheme.primary,
|
||||||
onClick = {
|
onClick = {
|
||||||
try {
|
try {
|
||||||
uriHandler.openUri("tel:${phone.replace(" ", "")}")
|
uriHandler.openUri("tel:${phone.replace(" ", "")}") // i18n-ignore: tel: URI, non-UI
|
||||||
} catch (e: Exception) { /* Handle error */ }
|
} catch (e: Exception) { /* Handle error */ }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -366,7 +367,7 @@ fun ContractorDetailScreen(
|
|||||||
iconTint = MaterialTheme.colorScheme.secondary,
|
iconTint = MaterialTheme.colorScheme.secondary,
|
||||||
onClick = {
|
onClick = {
|
||||||
try {
|
try {
|
||||||
uriHandler.openUri("mailto:$email")
|
uriHandler.openUri("mailto:$email") // i18n-ignore: mailto: URI, non-UI
|
||||||
} catch (e: Exception) { /* Handle error */ }
|
} catch (e: Exception) { /* Handle error */ }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -432,7 +433,7 @@ fun ContractorDetailScreen(
|
|||||||
contractor.stateProvince,
|
contractor.stateProvince,
|
||||||
contractor.postalCode
|
contractor.postalCode
|
||||||
).joinToString(", ")
|
).joinToString(", ")
|
||||||
uriHandler.openUri("geo:0,0?q=$address")
|
uriHandler.openUri("geo:0,0?q=$address") // i18n-ignore: geo: URI, non-UI
|
||||||
} catch (e: Exception) { /* Handle error */ }
|
} catch (e: Exception) { /* Handle error */ }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -444,7 +445,7 @@ fun ContractorDetailScreen(
|
|||||||
// Associated Property
|
// Associated Property
|
||||||
contractor.residenceId?.let { resId ->
|
contractor.residenceId?.let { resId ->
|
||||||
val residenceName = residences.find { r -> r.id == resId }?.name
|
val residenceName = residences.find { r -> r.id == resId }?.name
|
||||||
?: "Property #$resId"
|
?: "$propertyRefLabel$resId"
|
||||||
|
|
||||||
item {
|
item {
|
||||||
DetailSection(title = stringResource(Res.string.contractors_associated_property)) {
|
DetailSection(title = stringResource(Res.string.contractors_associated_property)) {
|
||||||
@@ -683,7 +684,7 @@ fun ClickableDetailRow(
|
|||||||
}
|
}
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.OpenInNew,
|
Icons.Default.OpenInNew,
|
||||||
contentDescription = "Open",
|
contentDescription = stringResource(Res.string.common_open),
|
||||||
modifier = Modifier.size(16.dp),
|
modifier = Modifier.size(16.dp),
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -295,8 +295,8 @@ fun DocumentDetailScreen(
|
|||||||
)
|
)
|
||||||
OrganicDivider()
|
OrganicDivider()
|
||||||
|
|
||||||
document.residenceId?.let { DetailRow(stringResource(Res.string.documents_residence), "Residence #$it") }
|
document.residenceId?.let { DetailRow(stringResource(Res.string.documents_residence), stringResource(Res.string.documents_residence_ref, it)) }
|
||||||
document.taskId?.let { DetailRow(stringResource(Res.string.documents_contractor), "Task #$it") }
|
document.taskId?.let { DetailRow(stringResource(Res.string.documents_contractor), stringResource(Res.string.documents_task_ref, it)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,7 +531,7 @@ fun DocumentImageViewer(
|
|||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = if (showFullImage) "Image ${selectedIndex + 1} of ${images.size}" else "Document Images",
|
text = if (showFullImage) stringResource(Res.string.documents_image_index, selectedIndex + 1, images.size) else stringResource(Res.string.documents_images_title),
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
@@ -544,7 +544,7 @@ fun DocumentImageViewer(
|
|||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Close,
|
Icons.Default.Close,
|
||||||
contentDescription = "Close"
|
contentDescription = stringResource(Res.string.common_close)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -594,17 +594,17 @@ fun DocumentImageViewer(
|
|||||||
onClick = { selectedIndex = (selectedIndex - 1 + images.size) % images.size },
|
onClick = { selectedIndex = (selectedIndex - 1 + images.size) % images.size },
|
||||||
enabled = selectedIndex > 0
|
enabled = selectedIndex > 0
|
||||||
) {
|
) {
|
||||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, "Previous")
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, stringResource(Res.string.documents_previous))
|
||||||
Spacer(modifier = Modifier.width(OrganicSpacing.sm))
|
Spacer(modifier = Modifier.width(OrganicSpacing.sm))
|
||||||
Text("Previous")
|
Text(stringResource(Res.string.documents_previous))
|
||||||
}
|
}
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onClick = { selectedIndex = (selectedIndex + 1) % images.size },
|
onClick = { selectedIndex = (selectedIndex + 1) % images.size },
|
||||||
enabled = selectedIndex < images.size - 1
|
enabled = selectedIndex < images.size - 1
|
||||||
) {
|
) {
|
||||||
Text("Next")
|
Text(stringResource(Res.string.documents_next))
|
||||||
Spacer(modifier = Modifier.width(OrganicSpacing.sm))
|
Spacer(modifier = Modifier.width(OrganicSpacing.sm))
|
||||||
Icon(Icons.AutoMirrored.Filled.ArrowForward, "Next")
|
Icon(Icons.AutoMirrored.Filled.ArrowForward, stringResource(Res.string.documents_next))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ fun EditTaskScreen(
|
|||||||
selectedFrequency = frequency
|
selectedFrequency = frequency
|
||||||
frequencyExpanded = false
|
frequencyExpanded = false
|
||||||
// Clear custom interval if not Custom frequency
|
// Clear custom interval if not Custom frequency
|
||||||
if (!frequency.name.equals("Custom", ignoreCase = true)) {
|
if (!frequency.name.equals("Custom", ignoreCase = true)) { // i18n-ignore: API enum value comparison, non-UI
|
||||||
customIntervalDays = ""
|
customIntervalDays = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -220,7 +220,7 @@ fun EditTaskScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Custom Interval Days (only for "Custom" frequency)
|
// Custom Interval Days (only for "Custom" frequency)
|
||||||
if (selectedFrequency?.name?.equals("Custom", ignoreCase = true) == true) {
|
if (selectedFrequency?.name?.equals("Custom", ignoreCase = true) == true) { // i18n-ignore: API enum value comparison, non-UI
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = customIntervalDays,
|
value = customIntervalDays,
|
||||||
onValueChange = { customIntervalDays = it.filter { char -> char.isDigit() } },
|
onValueChange = { customIntervalDays = it.filter { char -> char.isDigit() } },
|
||||||
@@ -325,7 +325,7 @@ fun EditTaskScreen(
|
|||||||
description = description.ifBlank { null },
|
description = description.ifBlank { null },
|
||||||
categoryId = selectedCategory!!.id,
|
categoryId = selectedCategory!!.id,
|
||||||
frequencyId = selectedFrequency!!.id,
|
frequencyId = selectedFrequency!!.id,
|
||||||
customIntervalDays = if (selectedFrequency?.name?.equals("Custom", ignoreCase = true) == true && customIntervalDays.isNotBlank()) {
|
customIntervalDays = if (selectedFrequency?.name?.equals("Custom", ignoreCase = true) == true && customIntervalDays.isNotBlank()) { // i18n-ignore: API enum value comparison, non-UI
|
||||||
customIntervalDays.toIntOrNull()
|
customIntervalDays.toIntOrNull()
|
||||||
} else null,
|
} else null,
|
||||||
priorityId = selectedPriority!!.id,
|
priorityId = selectedPriority!!.id,
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ fun ForgotPasswordScreen(
|
|||||||
viewModel.setEmail(email)
|
viewModel.setEmail(email)
|
||||||
viewModel.requestPasswordReset(email)
|
viewModel.requestPasswordReset(email)
|
||||||
},
|
},
|
||||||
errorTitle = "Failed to Send Reset Code"
|
errorTitle = stringResource(Res.string.forgot_send_failed_title)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handle automatic navigation to next step
|
// Handle automatic navigation to next step
|
||||||
@@ -138,7 +138,7 @@ fun ForgotPasswordScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
"We'll send a 6-digit verification code to this address",
|
stringResource(Res.string.forgot_send_code_hint),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.textSecondary,
|
color = MaterialTheme.colorScheme.textSecondary,
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
@@ -161,11 +161,11 @@ fun ForgotPasswordScreen(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.CheckCircle,
|
Icons.Default.CheckCircle,
|
||||||
contentDescription = "Success",
|
contentDescription = stringResource(Res.string.common_success),
|
||||||
tint = MaterialTheme.colorScheme.primary
|
tint = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
"Check your email for a 6-digit verification code",
|
stringResource(Res.string.forgot_check_email_msg),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.textPrimary
|
color = MaterialTheme.colorScheme.textPrimary
|
||||||
)
|
)
|
||||||
@@ -192,7 +192,7 @@ fun ForgotPasswordScreen(
|
|||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
"Remember your password? Back to Login",
|
stringResource(Res.string.forgot_back_to_login),
|
||||||
style = MaterialTheme.typography.bodyMedium
|
style = MaterialTheme.typography.bodyMedium
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ fun LoginScreen(
|
|||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = "or",
|
text = stringResource(Res.string.common_or),
|
||||||
modifier = Modifier.padding(horizontal = OrganicSpacing.lg),
|
modifier = Modifier.padding(horizontal = OrganicSpacing.lg),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ fun ManageUsersScreen(
|
|||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val clipboardManager = LocalClipboardManager.current
|
val clipboardManager = LocalClipboardManager.current
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
val codeCopiedMessage = stringResource(Res.string.manage_users_code_copied)
|
||||||
|
|
||||||
// Extracted so retry can reuse it.
|
// Extracted so retry can reuse it.
|
||||||
suspend fun loadUsers() {
|
suspend fun loadUsers() {
|
||||||
@@ -115,8 +116,8 @@ fun ManageUsersScreen(
|
|||||||
}
|
}
|
||||||
} else if (error != null) {
|
} else if (error != null) {
|
||||||
StandardErrorState(
|
StandardErrorState(
|
||||||
title = "Couldn't load users",
|
title = stringResource(Res.string.manage_users_load_failed),
|
||||||
message = error ?: "Unknown error",
|
message = error ?: stringResource(Res.string.error_generic),
|
||||||
onRetry = { scope.launch { loadUsers() } },
|
onRetry = { scope.launch { loadUsers() } },
|
||||||
modifier = Modifier.padding(paddingValues)
|
modifier = Modifier.padding(paddingValues)
|
||||||
)
|
)
|
||||||
@@ -245,13 +246,13 @@ fun ManageUsersScreen(
|
|||||||
onClick = {
|
onClick = {
|
||||||
clipboardManager.setText(AnnotatedString(shareCode!!.code))
|
clipboardManager.setText(AnnotatedString(shareCode!!.code))
|
||||||
scope.launch {
|
scope.launch {
|
||||||
snackbarHostState.showSnackbar("Code copied to clipboard")
|
snackbarHostState.showSnackbar(codeCopiedMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.ContentCopy,
|
Icons.Default.ContentCopy,
|
||||||
contentDescription = "Copy code",
|
contentDescription = stringResource(Res.string.manage_users_copy_code),
|
||||||
tint = MaterialTheme.colorScheme.primary
|
tint = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -341,7 +342,7 @@ fun ManageUsersScreen(
|
|||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { showRemoveConfirmation = null },
|
onDismissRequest = { showRemoveConfirmation = null },
|
||||||
title = { Text(stringResource(Res.string.manage_users_remove)) },
|
title = { Text(stringResource(Res.string.manage_users_remove)) },
|
||||||
text = { Text("Remove ${user.username} from this property?") },
|
text = { Text(stringResource(Res.string.manage_users_remove_confirm, user.username)) },
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
|
|||||||
+4
-4
@@ -296,7 +296,7 @@ fun NotificationPreferencesScreen(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Error,
|
Icons.Default.Error,
|
||||||
contentDescription = "Error",
|
contentDescription = stringResource(Res.string.common_error),
|
||||||
tint = MaterialTheme.colorScheme.error,
|
tint = MaterialTheme.colorScheme.error,
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
@@ -702,13 +702,13 @@ private fun HourPickerDialog(
|
|||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||||
) {
|
) {
|
||||||
HourColumn(label = "AM", range = 6..11, selectedHour = selectedHour) {
|
HourColumn(label = stringResource(Res.string.time_am), range = 6..11, selectedHour = selectedHour) {
|
||||||
selectedHour = it
|
selectedHour = it
|
||||||
}
|
}
|
||||||
HourColumn(label = "PM", range = 12..17, selectedHour = selectedHour) {
|
HourColumn(label = stringResource(Res.string.time_pm), range = 12..17, selectedHour = selectedHour) {
|
||||||
selectedHour = it
|
selectedHour = it
|
||||||
}
|
}
|
||||||
HourColumn(label = "EVE", range = 18..23, selectedHour = selectedHour) {
|
HourColumn(label = stringResource(Res.string.time_eve), range = 18..23, selectedHour = selectedHour) {
|
||||||
selectedHour = it
|
selectedHour = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import androidx.compose.ui.text.input.KeyboardType
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.tt.honeyDue.ui.components.HandleErrors
|
import com.tt.honeyDue.ui.components.HandleErrors
|
||||||
|
import com.tt.honeyDue.ui.screens.theme.themeDisplayName
|
||||||
import com.tt.honeyDue.ui.components.common.ErrorCard
|
import com.tt.honeyDue.ui.components.common.ErrorCard
|
||||||
import com.tt.honeyDue.ui.components.dialogs.DeleteAccountDialog
|
import com.tt.honeyDue.ui.components.dialogs.DeleteAccountDialog
|
||||||
import com.tt.honeyDue.utils.SubscriptionHelper
|
import com.tt.honeyDue.utils.SubscriptionHelper
|
||||||
@@ -267,7 +268,7 @@ fun ProfileScreen(
|
|||||||
fontWeight = FontWeight.SemiBold
|
fontWeight = FontWeight.SemiBold
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = currentTheme.displayName,
|
text = themeDisplayName(currentTheme.id),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.primary
|
color = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
@@ -379,7 +380,7 @@ fun ProfileScreen(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable {
|
.clickable {
|
||||||
uriHandler.openUri("mailto:honeydueSupport@treymail.com?subject=honeyDue%20Support%20Request")
|
uriHandler.openUri("mailto:honeydueSupport@treymail.com?subject=honeyDue%20Support%20Request") // i18n-ignore: mailto: support URI, non-UI
|
||||||
}
|
}
|
||||||
.naturalShadow()
|
.naturalShadow()
|
||||||
) {
|
) {
|
||||||
@@ -471,7 +472,7 @@ fun ProfileScreen(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Star,
|
imageVector = Icons.Default.Star,
|
||||||
contentDescription = "Subscription",
|
contentDescription = stringResource(Res.string.profile_subscription_cd),
|
||||||
tint = if (SubscriptionHelper.currentTier == "pro") MaterialTheme.colorScheme.tertiary else MaterialTheme.colorScheme.onSurfaceVariant
|
tint = if (SubscriptionHelper.currentTier == "pro") MaterialTheme.colorScheme.tertiary else MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -636,7 +637,7 @@ fun ProfileScreen(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Error,
|
Icons.Default.Error,
|
||||||
contentDescription = "Error",
|
contentDescription = stringResource(Res.string.common_error),
|
||||||
tint = MaterialTheme.colorScheme.error
|
tint = MaterialTheme.colorScheme.error
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
@@ -666,7 +667,7 @@ fun ProfileScreen(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.CheckCircle,
|
Icons.Default.CheckCircle,
|
||||||
contentDescription = "Success",
|
contentDescription = stringResource(Res.string.common_success),
|
||||||
tint = MaterialTheme.colorScheme.onPrimaryContainer
|
tint = MaterialTheme.colorScheme.onPrimaryContainer
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
+11
-11
@@ -43,7 +43,7 @@ fun ResetPasswordScreen(
|
|||||||
// Handle errors for password reset
|
// Handle errors for password reset
|
||||||
resetPasswordState.HandleErrors(
|
resetPasswordState.HandleErrors(
|
||||||
onRetry = { viewModel.resetPassword(newPassword, confirmPassword) },
|
onRetry = { viewModel.resetPassword(newPassword, confirmPassword) },
|
||||||
errorTitle = "Password Reset Failed"
|
errorTitle = stringResource(Res.string.reset_pw_failed_title)
|
||||||
)
|
)
|
||||||
|
|
||||||
val errorMessage = when (resetPasswordState) {
|
val errorMessage = when (resetPasswordState) {
|
||||||
@@ -116,7 +116,7 @@ fun ResetPasswordScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "Success!",
|
text = stringResource(Res.string.common_success),
|
||||||
style = MaterialTheme.typography.headlineMedium,
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = MaterialTheme.colorScheme.textPrimary,
|
color = MaterialTheme.colorScheme.textPrimary,
|
||||||
@@ -124,7 +124,7 @@ fun ResetPasswordScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "Your password has been reset successfully",
|
text = stringResource(Res.string.reset_pw_success_msg),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.textSecondary,
|
color = MaterialTheme.colorScheme.textSecondary,
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
@@ -136,7 +136,7 @@ fun ResetPasswordScreen(
|
|||||||
showBlob = false
|
showBlob = false
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
"You can now log in with your new password",
|
stringResource(Res.string.reset_pw_can_login),
|
||||||
modifier = Modifier.padding(OrganicSpacing.cozy),
|
modifier = Modifier.padding(OrganicSpacing.cozy),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.textPrimary,
|
color = MaterialTheme.colorScheme.textPrimary,
|
||||||
@@ -149,7 +149,7 @@ fun ResetPasswordScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
OrganicPrimaryButton(
|
OrganicPrimaryButton(
|
||||||
text = "Return to Login",
|
text = stringResource(Res.string.reset_return_to_login),
|
||||||
onClick = onPasswordResetSuccess
|
onClick = onPasswordResetSuccess
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -163,7 +163,7 @@ fun ResetPasswordScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "Set New Password",
|
text = stringResource(Res.string.reset_set_new_pw_title),
|
||||||
style = MaterialTheme.typography.headlineMedium,
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = MaterialTheme.colorScheme.textPrimary,
|
color = MaterialTheme.colorScheme.textPrimary,
|
||||||
@@ -171,7 +171,7 @@ fun ResetPasswordScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "Create a strong password to secure your account",
|
text = stringResource(Res.string.reset_create_strong_pw),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.textSecondary,
|
color = MaterialTheme.colorScheme.textSecondary,
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
@@ -190,7 +190,7 @@ fun ResetPasswordScreen(
|
|||||||
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.compact)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.compact)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
"Password Requirements",
|
stringResource(Res.string.reset_pw_requirements),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
color = MaterialTheme.colorScheme.textPrimary
|
color = MaterialTheme.colorScheme.textPrimary
|
||||||
@@ -233,7 +233,7 @@ fun ResetPasswordScreen(
|
|||||||
IconButton(onClick = { newPasswordVisible = !newPasswordVisible }) {
|
IconButton(onClick = { newPasswordVisible = !newPasswordVisible }) {
|
||||||
Icon(
|
Icon(
|
||||||
if (newPasswordVisible) Icons.Default.Visibility else Icons.Default.VisibilityOff,
|
if (newPasswordVisible) Icons.Default.Visibility else Icons.Default.VisibilityOff,
|
||||||
contentDescription = if (newPasswordVisible) "Hide password" else "Show password"
|
contentDescription = if (newPasswordVisible) stringResource(Res.string.auth_hide_password) else stringResource(Res.string.auth_show_password)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -257,7 +257,7 @@ fun ResetPasswordScreen(
|
|||||||
IconButton(onClick = { confirmPasswordVisible = !confirmPasswordVisible }) {
|
IconButton(onClick = { confirmPasswordVisible = !confirmPasswordVisible }) {
|
||||||
Icon(
|
Icon(
|
||||||
if (confirmPasswordVisible) Icons.Default.Visibility else Icons.Default.VisibilityOff,
|
if (confirmPasswordVisible) Icons.Default.Visibility else Icons.Default.VisibilityOff,
|
||||||
contentDescription = if (confirmPasswordVisible) "Hide password" else "Show password"
|
contentDescription = if (confirmPasswordVisible) stringResource(Res.string.auth_hide_password) else stringResource(Res.string.auth_show_password)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -274,7 +274,7 @@ fun ResetPasswordScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
OrganicPrimaryButton(
|
OrganicPrimaryButton(
|
||||||
text = if (isLoggingIn) "Logging in..." else stringResource(Res.string.auth_reset_button),
|
text = if (isLoggingIn) stringResource(Res.string.auth_logging_in) else stringResource(Res.string.auth_reset_button),
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.resetPassword(newPassword, confirmPassword)
|
viewModel.resetPassword(newPassword, confirmPassword)
|
||||||
},
|
},
|
||||||
|
|||||||
+5
-5
@@ -402,7 +402,7 @@ fun ResidenceDetailScreen(
|
|||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { showShareError = false },
|
onDismissRequest = { showShareError = false },
|
||||||
title = { Text(stringResource(Res.string.common_error)) },
|
title = { Text(stringResource(Res.string.common_error)) },
|
||||||
text = { Text(shareState.error ?: "Failed to share residence") },
|
text = { Text(shareState.error ?: stringResource(Res.string.residence_share_failed)) },
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = { showShareError = false }) {
|
TextButton(onClick = { showShareError = false }) {
|
||||||
Text(stringResource(Res.string.common_ok))
|
Text(stringResource(Res.string.common_ok))
|
||||||
@@ -666,13 +666,13 @@ fun ResidenceDetailScreen(
|
|||||||
}
|
}
|
||||||
if (residence.apartmentUnit != null) {
|
if (residence.apartmentUnit != null) {
|
||||||
Text(
|
Text(
|
||||||
text = "Unit: ${residence.apartmentUnit}",
|
text = stringResource(Res.string.residence_unit_label, residence.apartmentUnit),
|
||||||
color = MaterialTheme.colorScheme.textSecondary
|
color = MaterialTheme.colorScheme.textSecondary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (residence.city != null || residence.stateProvince != null || residence.postalCode != null) {
|
if (residence.city != null || residence.stateProvince != null || residence.postalCode != null) {
|
||||||
Text(
|
Text(
|
||||||
text = "${residence.city ?: ""}, ${residence.stateProvince ?: ""} ${residence.postalCode ?: ""}",
|
text = "${residence.city ?: ""}, ${residence.stateProvince ?: ""} ${residence.postalCode ?: ""}", // i18n-ignore: address data concatenation, no translatable prose
|
||||||
color = MaterialTheme.colorScheme.textSecondary
|
color = MaterialTheme.colorScheme.textSecondary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -876,7 +876,7 @@ fun ResidenceDetailScreen(
|
|||||||
is ApiResult.Error -> {
|
is ApiResult.Error -> {
|
||||||
item {
|
item {
|
||||||
CompactErrorState(
|
CompactErrorState(
|
||||||
message = "Error loading tasks: ${com.tt.honeyDue.util.ErrorMessageParser.parse((tasksState as ApiResult.Error).message)}",
|
message = stringResource(Res.string.residence_error_loading_tasks, com.tt.honeyDue.util.ErrorMessageParser.parse((tasksState as ApiResult.Error).message)),
|
||||||
onRetry = { residenceViewModel.loadResidenceTasks(residenceId) }
|
onRetry = { residenceViewModel.loadResidenceTasks(residenceId) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1011,7 +1011,7 @@ fun ResidenceDetailScreen(
|
|||||||
is ApiResult.Error -> {
|
is ApiResult.Error -> {
|
||||||
item {
|
item {
|
||||||
CompactErrorState(
|
CompactErrorState(
|
||||||
message = "Error loading contractors: ${com.tt.honeyDue.util.ErrorMessageParser.parse((contractorsState as ApiResult.Error).message)}",
|
message = stringResource(Res.string.residence_error_loading_contractors, com.tt.honeyDue.util.ErrorMessageParser.parse((contractorsState as ApiResult.Error).message)),
|
||||||
onRetry = { residenceViewModel.loadResidenceContractors(residenceId) }
|
onRetry = { residenceViewModel.loadResidenceContractors(residenceId) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -390,7 +390,7 @@ fun ResidenceFormScreen(
|
|||||||
if (isEditMode && isCurrentUserOwner) {
|
if (isEditMode && isCurrentUserOwner) {
|
||||||
OrganicDivider()
|
OrganicDivider()
|
||||||
Text(
|
Text(
|
||||||
text = "Shared Users (${users.size})",
|
text = stringResource(Res.string.properties_shared_users_count, users.size),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
color = MaterialTheme.colorScheme.primary
|
color = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
@@ -404,7 +404,7 @@ fun ResidenceFormScreen(
|
|||||||
}
|
}
|
||||||
} else if (users.isEmpty()) {
|
} else if (users.isEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
text = "No shared users",
|
text = stringResource(Res.string.properties_no_shared_users),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
modifier = Modifier.padding(vertical = OrganicSpacing.compact)
|
modifier = Modifier.padding(vertical = OrganicSpacing.compact)
|
||||||
@@ -422,7 +422,7 @@ fun ResidenceFormScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "Users with access to this residence. Use the share button to invite others.",
|
text = stringResource(Res.string.properties_shared_users_helper),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
modifier = Modifier.padding(top = 4.dp)
|
modifier = Modifier.padding(top = 4.dp)
|
||||||
@@ -485,9 +485,9 @@ fun ResidenceFormScreen(
|
|||||||
showRemoveUserConfirmation = false
|
showRemoveUserConfirmation = false
|
||||||
userToRemove = null
|
userToRemove = null
|
||||||
},
|
},
|
||||||
title = { Text("Remove User") },
|
title = { Text(stringResource(Res.string.properties_remove_user)) },
|
||||||
text = {
|
text = {
|
||||||
Text("Are you sure you want to remove ${userToRemove?.username} from this residence?")
|
Text(stringResource(Res.string.properties_remove_user_confirm, userToRemove?.username ?: ""))
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(
|
TextButton(
|
||||||
@@ -514,7 +514,7 @@ fun ResidenceFormScreen(
|
|||||||
contentColor = MaterialTheme.colorScheme.error
|
contentColor = MaterialTheme.colorScheme.error
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Text("Remove")
|
Text(stringResource(Res.string.properties_remove_button))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
@@ -524,7 +524,7 @@ fun ResidenceFormScreen(
|
|||||||
userToRemove = null
|
userToRemove = null
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Text("Cancel")
|
Text(stringResource(Res.string.common_cancel))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -572,7 +572,7 @@ private fun UserListItem(
|
|||||||
IconButton(onClick = onRemove) {
|
IconButton(onClick = onRemove) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Delete,
|
Icons.Default.Delete,
|
||||||
contentDescription = "Remove user",
|
contentDescription = stringResource(Res.string.manage_users_remove_user),
|
||||||
tint = MaterialTheme.colorScheme.error
|
tint = MaterialTheme.colorScheme.error
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -327,7 +327,7 @@ fun ResidencesScreen(
|
|||||||
) {
|
) {
|
||||||
Icon(Icons.Default.Star, contentDescription = null) // decorative
|
Icon(Icons.Default.Star, contentDescription = null) // decorative
|
||||||
Text(
|
Text(
|
||||||
"Upgrade to Add",
|
stringResource(Res.string.residences_upgrade_to_add),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.SemiBold
|
fontWeight = FontWeight.SemiBold
|
||||||
)
|
)
|
||||||
@@ -462,7 +462,7 @@ fun ResidencesScreen(
|
|||||||
animation = tween(800, easing = EaseInOut),
|
animation = tween(800, easing = EaseInOut),
|
||||||
repeatMode = RepeatMode.Reverse
|
repeatMode = RepeatMode.Reverse
|
||||||
),
|
),
|
||||||
label = "pulseScale"
|
label = "pulseScale" // i18n-ignore: animation label, non-UI
|
||||||
)
|
)
|
||||||
|
|
||||||
OrganicCard(
|
OrganicCard(
|
||||||
@@ -570,7 +570,7 @@ fun ResidencesScreen(
|
|||||||
if (residence.isPrimary) {
|
if (residence.isPrimary) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Star,
|
Icons.Default.Star,
|
||||||
contentDescription = "Primary residence",
|
contentDescription = stringResource(Res.string.residences_primary_cd),
|
||||||
modifier = Modifier.size(16.dp),
|
modifier = Modifier.size(16.dp),
|
||||||
tint = Color(0xFFFFD700) // Gold color
|
tint = Color(0xFFFFD700) // Gold color
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ fun TasksScreen(
|
|||||||
verticalAlignment = androidx.compose.ui.Alignment.CenterVertically
|
verticalAlignment = androidx.compose.ui.Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
OrganicIconContainer(
|
OrganicIconContainer(
|
||||||
icon = getIconFromName(column.icons["android"] ?: "List"),
|
icon = getIconFromName(column.icons["android"] ?: "List"), // i18n-ignore: icon name identifier, non-UI
|
||||||
size = 40.dp,
|
size = 40.dp,
|
||||||
iconScale = 0.5f,
|
iconScale = 0.5f,
|
||||||
backgroundColor = hexToColor(column.color).copy(alpha = 0.2f),
|
backgroundColor = hexToColor(column.color).copy(alpha = 0.2f),
|
||||||
@@ -251,7 +251,7 @@ fun TasksScreen(
|
|||||||
}
|
}
|
||||||
Icon(
|
Icon(
|
||||||
if (isExpanded) Icons.Default.KeyboardArrowUp else Icons.Default.KeyboardArrowDown,
|
if (isExpanded) Icons.Default.KeyboardArrowUp else Icons.Default.KeyboardArrowDown,
|
||||||
contentDescription = if (isExpanded) "Collapse" else "Expand",
|
contentDescription = if (isExpanded) stringResource(Res.string.common_collapse) else stringResource(Res.string.common_expand),
|
||||||
tint = MaterialTheme.colorScheme.textSecondary
|
tint = MaterialTheme.colorScheme.textSecondary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,10 +41,12 @@ fun VerifyEmailScreen(
|
|||||||
|
|
||||||
val verifyState by viewModel.verifyEmailState.collectAsStateWithLifecycle()
|
val verifyState by viewModel.verifyEmailState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
val invalidCodeMessage = stringResource(Res.string.verify_email_invalid_code)
|
||||||
|
|
||||||
// Handle errors for email verification
|
// Handle errors for email verification
|
||||||
verifyState.HandleErrors(
|
verifyState.HandleErrors(
|
||||||
onRetry = { viewModel.verifyEmail(code) },
|
onRetry = { viewModel.verifyEmail(code) },
|
||||||
errorTitle = "Verification Failed"
|
errorTitle = stringResource(Res.string.verify_email_failed_title)
|
||||||
)
|
)
|
||||||
|
|
||||||
LaunchedEffect(verifyState) {
|
LaunchedEffect(verifyState) {
|
||||||
@@ -147,11 +149,11 @@ fun VerifyEmailScreen(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Info,
|
Icons.Default.Info,
|
||||||
contentDescription = "Info",
|
contentDescription = stringResource(Res.string.common_info),
|
||||||
tint = MaterialTheme.colorScheme.error
|
tint = MaterialTheme.colorScheme.error
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = "Email verification is required. Check your inbox for a 6-digit code.",
|
text = stringResource(Res.string.verify_email_required_msg),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.textPrimary,
|
color = MaterialTheme.colorScheme.textPrimary,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
@@ -196,7 +198,7 @@ fun VerifyEmailScreen(
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
viewModel.verifyEmail(code)
|
viewModel.verifyEmail(code)
|
||||||
} else {
|
} else {
|
||||||
errorMessage = "Please enter a valid 6-digit code"
|
errorMessage = invalidCodeMessage
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier.testTag(AccessibilityIds.Authentication.verifyButton),
|
modifier = Modifier.testTag(AccessibilityIds.Authentication.verifyButton),
|
||||||
@@ -207,7 +209,7 @@ fun VerifyEmailScreen(
|
|||||||
Spacer(modifier = Modifier.height(OrganicSpacing.compact))
|
Spacer(modifier = Modifier.height(OrganicSpacing.compact))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "Didn't receive the code? Check your spam folder or contact support.",
|
text = stringResource(Res.string.verify_email_didnt_receive),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.textSecondary,
|
color = MaterialTheme.colorScheme.textSecondary,
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
|
|||||||
+9
-9
@@ -38,7 +38,7 @@ fun VerifyResetCodeScreen(
|
|||||||
// Handle errors for code verification
|
// Handle errors for code verification
|
||||||
verifyCodeState.HandleErrors(
|
verifyCodeState.HandleErrors(
|
||||||
onRetry = { viewModel.verifyResetCode(email, code) },
|
onRetry = { viewModel.verifyResetCode(email, code) },
|
||||||
errorTitle = "Code Verification Failed"
|
errorTitle = stringResource(Res.string.reset_verify_failed_title)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handle automatic navigation to next step
|
// Handle automatic navigation to next step
|
||||||
@@ -102,7 +102,7 @@ fun VerifyResetCodeScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "Check Your Email",
|
text = stringResource(Res.string.reset_check_email_title),
|
||||||
style = MaterialTheme.typography.headlineMedium,
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = MaterialTheme.colorScheme.textPrimary,
|
color = MaterialTheme.colorScheme.textPrimary,
|
||||||
@@ -110,7 +110,7 @@ fun VerifyResetCodeScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "We sent a 6-digit code to",
|
text = stringResource(Res.string.reset_sent_code_to),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.textSecondary,
|
color = MaterialTheme.colorScheme.textSecondary,
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
@@ -142,7 +142,7 @@ fun VerifyResetCodeScreen(
|
|||||||
tint = MaterialTheme.colorScheme.secondary
|
tint = MaterialTheme.colorScheme.secondary
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
"Code expires in 15 minutes",
|
stringResource(Res.string.reset_code_expires),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
color = MaterialTheme.colorScheme.textPrimary
|
color = MaterialTheme.colorScheme.textPrimary
|
||||||
@@ -173,7 +173,7 @@ fun VerifyResetCodeScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
"Enter the 6-digit code from your email",
|
stringResource(Res.string.reset_enter_code_hint),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.textSecondary,
|
color = MaterialTheme.colorScheme.textSecondary,
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
@@ -196,11 +196,11 @@ fun VerifyResetCodeScreen(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.CheckCircle,
|
Icons.Default.CheckCircle,
|
||||||
contentDescription = "Verified",
|
contentDescription = stringResource(Res.string.common_verified),
|
||||||
tint = MaterialTheme.colorScheme.primary
|
tint = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
"Code verified! Now set your new password",
|
stringResource(Res.string.reset_code_verified_msg),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.textPrimary
|
color = MaterialTheme.colorScheme.textPrimary
|
||||||
)
|
)
|
||||||
@@ -226,7 +226,7 @@ fun VerifyResetCodeScreen(
|
|||||||
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.compact)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.compact)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
"Didn't receive the code?",
|
stringResource(Res.string.reset_didnt_receive),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.textSecondary
|
color = MaterialTheme.colorScheme.textSecondary
|
||||||
)
|
)
|
||||||
@@ -245,7 +245,7 @@ fun VerifyResetCodeScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
"Check your spam folder if you don't see it",
|
stringResource(Res.string.reset_check_spam),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.textSecondary,
|
color = MaterialTheme.colorScheme.textSecondary,
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user