diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 62ab9e976..4f7ff8cb2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,6 +30,7 @@ jobs: uses: codecov/codecov-action@v3 ios: runs-on: macos-latest + if: ${{ false }} steps: - name: Checkout uses: actions/checkout@v3 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 000000000..81106b217 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,15 @@ +name: Publish +on: + release: + types: [ published ] + +jobs: + notify: + runs-on: ubuntu-latest + steps: + - name: Discord notification + env: + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} + uses: Ilshidur/action-discord@master + with: + args: '**New release:** {{ EVENT_PAYLOAD.release.name }} **Download Here:** {{ EVENT_PAYLOAD.release.html_url }}' diff --git a/README.md b/README.md index 607d73250..e4ef53c56 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,19 @@ # ![Hammer Logo](readme/logo.png) Hammer: A story editor +A simple tool for building stories. + ![MIT License](https://img.shields.io/github/license/Wavesonics/hammer-editor) [![CircleCI](https://img.shields.io/circleci/build/github/Wavesonics/hammer-editor/develop)](https://app.circleci.com/pipelines/github/Wavesonics/hammer-editor) [![codebeat badge](https://codebeat.co/badges/ff1a14c8-352e-495f-8b61-2d5d46061149)](https://codebeat.co/projects/github-com-wavesonics-hammer-editor-master) [![codecov](https://codecov.io/gh/Wavesonics/hammer-editor/branch/develop/graph/badge.svg)](https://codecov.io/gh/Wavesonics/hammer-editor) ![Latest Release](https://img.shields.io/github/v/release/Wavesonics/hammer-editor?include_prereleases) ![badge-platform-android] ![badge-platform-windows] ![badge-platform-linux] -[![Discord badge](https://img.shields.io/discord/1100282852295327744)](https://discord.gg/GTmgjZcupk) +[![Discord badge](https://img.shields.io/discord/1100282852295327744?logo=discord)](https://discord.gg/GTmgjZcupk) +[![Localization badge](https://hosted.weblate.org/widgets/hammer/-/clients/svg-badge.svg)](https://hosted.weblate.org/engage/hammer/) -A simple tool for building stories. +### _**Note:** This is currently Alpha quality software, users beware!_ + +Take a look at the [Roadmap](ROADMAP.md) to see whats coming. + +Join our [Discord](https://discord.gg/GTmgjZcupk) and help us by reporting bugs, making feature requests, and discussing the future of Hammer. ## Multi-platform diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 000000000..70072c19f --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,53 @@ +# Roadmap + +_**Note:** This is a one-man side project, so development velocity is slow and uneven. This roadmap is aspirational, +nothing +on here is a guaranteed, and I can't be sure when any of it will actually happen._ + +## Near-term + +- **Client/Server syncing stabilization:** There's no known bugs, but this is by far the most likely feature to cause + user data loss, so it must be rock solid +- **Localization Support:** Move all strings into localization files, get setup with community localization service. +- **Quality of Life UI improvements:** The UI is ready for a second polish pass, focusing on quality of life + improvements. + - Word Count in text editor + - Draft selection editor + - Lots of work on Merge Conflict editors when syncing + - General work for larger screen sizes + - Confirm save on more entity types beyond just scenes + +## Road to 1.0 + +- **Rich Text Editor 2.0:** This is one of the biggest weaknesses currently. The current text editor is buggy, has poor + performance with lots of text, and only supports rich text through ugly hacks. We're waiting on `BasicTextfield2` + coming later this year to build a more robust editor on. + - Spell check + - Possibly grammar check +- **Encyclopedia Improvements:** usability improvements + - Allow more characters to be used in the name + - Allow changing of entry type after creation + - Allow tag removal, addition after creation + - Tag search in browser +- More unit testing across the board +- Release for **MacOS** + +## Post 1.0 + +- Release for **iOS** +- **Outlines:** write a short outline for each scene. Be able to see an overview of your story by reading only your + outlines in order. +- **Hemingway Mode:** Provide a distraction free writing experience. Very little UI, no spell check, ect. +- **Scene Notes:** add extra notes to a scene, remind your self what story beats to hit, or what tone to strike. +- **Scene/Encyclopedia Integration:** + - When you type the name of an entry in your encyclopedia, it will be hyper linked in-line to the entry. + - See a summary of which characters or locations appear in the scene +- **Story Insights**: Which scene two characters first appeared in together, possibly other stuff pulling from + encyclopedia and scenes +- **Encyclopedia 2.0:** Type specific templates, different fields for characters, locations, and more + +## Further Future + +- **Editor Requests:** Create a request for someone to edit a scene. A link to generated, and they can read and suggest + edits to the scene from a webpage. Then in-app, you can receive the edits and choose what to take. +- **Publish on Web:** Publish a story as a simple webview you can link people to. \ No newline at end of file diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index 6883ae8e0..0cd280e59 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -8,6 +8,7 @@ android:name=".HammerApplication" android:allowBackup="true" android:icon="@mipmap/ic_launcher" + android:roundIcon="@mipmap/ic_launcher_round" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/Theme.AppCompat.DayNight.NoActionBar" diff --git a/android/src/main/kotlin/com/darkrockstudios/apps/hammer/android/ProjectRootActivity.kt b/android/src/main/kotlin/com/darkrockstudios/apps/hammer/android/ProjectRootActivity.kt index 7fbfdd322..5bb9b8b74 100644 --- a/android/src/main/kotlin/com/darkrockstudios/apps/hammer/android/ProjectRootActivity.kt +++ b/android/src/main/kotlin/com/darkrockstudios/apps/hammer/android/ProjectRootActivity.kt @@ -21,6 +21,7 @@ import com.darkrockstudios.apps.hammer.common.AppCloseManager import com.darkrockstudios.apps.hammer.common.components.projectroot.ProjectRoot import com.darkrockstudios.apps.hammer.common.components.projectroot.ProjectRootComponent import com.darkrockstudios.apps.hammer.common.compose.Ui +import com.darkrockstudios.apps.hammer.common.compose.moko.get import com.darkrockstudios.apps.hammer.common.compose.theme.AppTheme import com.darkrockstudios.apps.hammer.common.data.MenuDescriptor import com.darkrockstudios.apps.hammer.common.data.ProjectDef @@ -163,10 +164,10 @@ class ProjectRootActivity : AppCompatActivity() { icon = { Icon( imageVector = getDestinationIcon(item), - contentDescription = item.text + contentDescription = item.text.get() ) }, - label = { Text(item.text) }, + label = { Text(item.text.get()) }, selected = router.active.instance.getLocationType() == item, onClick = { scope.launch { drawerState.close() } @@ -186,11 +187,11 @@ class ProjectRootActivity : AppCompatActivity() { private fun confirmCloseDialog(component: AppCloseManager) { AlertDialog.Builder(this) - .setTitle("Unsaved Scenes") - .setMessage("Save unsaved scenes?") - .setNegativeButton("Discard and close") { _, _ -> finish() } - .setNeutralButton("Cancel") { dialog, _ -> dialog.dismiss() } - .setPositiveButton("Save and close") { _, _ -> + .setTitle(R.string.unsaved_scenes_dialog_title) + .setMessage(R.string.unsaved_scenes_dialog_message) + .setNegativeButton(R.string.unsaved_scenes_dialog_negative_button) { _, _ -> finish() } + .setNeutralButton(R.string.unsaved_scenes_dialog_neutral_button) { dialog, _ -> dialog.dismiss() } + .setPositiveButton(R.string.unsaved_scenes_dialog_positive_button) { _, _ -> lifecycleScope.launch { component.storeDirtyBuffers() withContext(mainDispatcher) { diff --git a/android/src/main/kotlin/com/darkrockstudios/apps/hammer/android/ProjectSelectActivity.kt b/android/src/main/kotlin/com/darkrockstudios/apps/hammer/android/ProjectSelectActivity.kt index 2989758b0..87ebadf84 100644 --- a/android/src/main/kotlin/com/darkrockstudios/apps/hammer/android/ProjectSelectActivity.kt +++ b/android/src/main/kotlin/com/darkrockstudios/apps/hammer/android/ProjectSelectActivity.kt @@ -22,6 +22,7 @@ import com.darkrockstudios.apps.hammer.base.BuildMetadata import com.darkrockstudios.apps.hammer.common.components.projectselection.ProjectSelection import com.darkrockstudios.apps.hammer.common.components.projectselection.ProjectSelectionComponent import com.darkrockstudios.apps.hammer.common.compose.Ui +import com.darkrockstudios.apps.hammer.common.compose.moko.get import com.darkrockstudios.apps.hammer.common.compose.theme.AppTheme import com.darkrockstudios.apps.hammer.common.data.ProjectDef import com.darkrockstudios.apps.hammer.common.data.globalsettings.GlobalSettingsRepository @@ -103,7 +104,7 @@ class ProjectSelectActivity : AppCompatActivity() { topBar = { SetStatusBar() TopBar( - title = "Hammer", + title = getString(R.string.app_name), drawerOpen = drawerState, onButtonClicked = { scope.launch { @@ -125,7 +126,7 @@ class ProjectSelectActivity : AppCompatActivity() { Spacer(Modifier.height(12.dp)) ProjectSelection.Locations.values().forEach { item -> NavigationDrawerItem( - icon = { Icon(getLocationIcon(item), contentDescription = item.text) }, + icon = { Icon(getLocationIcon(item), contentDescription = item.text.get()) }, label = { Text(item.name) }, selected = item == slot.child?.configuration?.location, onClick = { diff --git a/android/src/main/kotlin/com/darkrockstudios/apps/hammer/android/TopAppBarDropdownMenu.kt b/android/src/main/kotlin/com/darkrockstudios/apps/hammer/android/TopAppBarDropdownMenu.kt index 4b554dbc3..21ec5f0c9 100644 --- a/android/src/main/kotlin/com/darkrockstudios/apps/hammer/android/TopAppBarDropdownMenu.kt +++ b/android/src/main/kotlin/com/darkrockstudios/apps/hammer/android/TopAppBarDropdownMenu.kt @@ -10,6 +10,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import com.darkrockstudios.apps.hammer.common.data.MenuDescriptor @Composable @@ -27,7 +28,7 @@ fun TopAppBarDropdownMenu( }) { Icon( Icons.Filled.MoreVert, - contentDescription = "More Menu", + contentDescription = stringResource(R.string.overflow_menu_button), tint = MaterialTheme.colorScheme.onSurface ) } diff --git a/android/src/main/kotlin/com/darkrockstudios/apps/hammer/android/TopBar.kt b/android/src/main/kotlin/com/darkrockstudios/apps/hammer/android/TopBar.kt index c737969a6..eb44ff4f5 100644 --- a/android/src/main/kotlin/com/darkrockstudios/apps/hammer/android/TopBar.kt +++ b/android/src/main/kotlin/com/darkrockstudios/apps/hammer/android/TopBar.kt @@ -7,6 +7,7 @@ import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.MenuOpen import androidx.compose.material3.* import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource import com.darkrockstudios.apps.hammer.common.compose.Ui @OptIn(ExperimentalMaterial3Api::class) @@ -36,7 +37,7 @@ internal fun TopBar( IconButton(onClick = { onButtonClicked() }) { Icon( icon, - contentDescription = "Nav Drawer", + contentDescription = stringResource(R.string.navdrawer_button), ) } }, diff --git a/android/src/main/kotlin/com/darkrockstudios/apps/hammer/android/widgets/AddNoteActivity.kt b/android/src/main/kotlin/com/darkrockstudios/apps/hammer/android/widgets/AddNoteActivity.kt index c36de68af..24ae0eb2e 100644 --- a/android/src/main/kotlin/com/darkrockstudios/apps/hammer/android/widgets/AddNoteActivity.kt +++ b/android/src/main/kotlin/com/darkrockstudios/apps/hammer/android/widgets/AddNoteActivity.kt @@ -12,12 +12,14 @@ import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment.Companion.End import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp import androidx.work.Data import androidx.work.OneTimeWorkRequestBuilder import androidx.work.OutOfQuotaPolicy import androidx.work.WorkManager +import com.darkrockstudios.apps.hammer.android.R import com.darkrockstudios.apps.hammer.common.compose.Ui import com.darkrockstudios.apps.hammer.common.compose.theme.AppTheme import com.darkrockstudios.apps.hammer.common.data.ProjectDef @@ -37,7 +39,7 @@ class AddNoteActivity : ComponentActivity(), KoinComponent { val projects = projectsRepository.getProjects() if (projects.isEmpty()) { - Toast.makeText(this, "No projects", Toast.LENGTH_SHORT).show() + Toast.makeText(this, getString(R.string.note_widget_toast_no_projects), Toast.LENGTH_SHORT).show() finish() } else { setContent { @@ -53,7 +55,7 @@ class AddNoteActivity : ComponentActivity(), KoinComponent { modifier = Modifier.padding(Ui.Padding.XL) ) { Text( - "Add Note to:", + stringResource(R.string.note_widget_dialog_title), style = MaterialTheme.typography.headlineMedium, color = MaterialTheme.colorScheme.onBackground ) @@ -75,7 +77,7 @@ class AddNoteActivity : ComponentActivity(), KoinComponent { } } ) { - Text("Save") + Text(stringResource(R.string.note_widget_dialog_save_button)) } } } @@ -107,7 +109,7 @@ class AddNoteActivity : ComponentActivity(), KoinComponent { readOnly = true, value = selectedOptionText, onValueChange = { selectedOptionText = it }, - label = { Text("Categories") }, + label = { Text(stringResource(R.string.note_widget_dialog_categories_dropdown)) }, trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon( expanded = expanded @@ -147,7 +149,7 @@ class AddNoteActivity : ComponentActivity(), KoinComponent { WorkManager.getInstance(this).enqueue(request) - Toast.makeText(this, "Note Added", Toast.LENGTH_SHORT).show() + Toast.makeText(this, getString(R.string.note_widget_toast_success), Toast.LENGTH_SHORT).show() finish() } diff --git a/android/src/main/kotlin/com/darkrockstudios/apps/hammer/android/widgets/AddNoteWidget.kt b/android/src/main/kotlin/com/darkrockstudios/apps/hammer/android/widgets/AddNoteWidget.kt index d992cdf53..170c0bfd6 100644 --- a/android/src/main/kotlin/com/darkrockstudios/apps/hammer/android/widgets/AddNoteWidget.kt +++ b/android/src/main/kotlin/com/darkrockstudios/apps/hammer/android/widgets/AddNoteWidget.kt @@ -5,6 +5,7 @@ import android.content.Intent import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.glance.GlanceId import androidx.glance.GlanceModifier @@ -42,7 +43,7 @@ class AddNoteWidget : GlanceAppWidget() { Column { Image( ImageProvider(resId = R.drawable.ic_add_note), - contentDescription = "Add Note", + contentDescription = stringResource(R.string.note_widget_button_description), modifier = GlanceModifier.fillMaxSize().clickable( onClick = actionRunCallback() ) @@ -62,7 +63,7 @@ class AddNoteClickAction : ActionCallback { val intent = Intent(context, AddNoteActivity::class.java) .setFlags(FLAG_ACTIVITY_NEW_TASK) - .putExtra(AddNoteActivity.EXTRA_PROJECT_NAME, "Alice In Wonderland") + //.putExtra(AddNoteActivity.EXTRA_PROJECT_NAME, "Alice In Wonderland") context.startActivity(intent) } } \ No newline at end of file diff --git a/android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index ec4a1f00a..d053b86bc 100644 --- a/android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -2,4 +2,26 @@ + + + + + + + + + \ No newline at end of file diff --git a/android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index ec4a1f00a..d053b86bc 100644 --- a/android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -2,4 +2,26 @@ + + + + + + + + + \ No newline at end of file diff --git a/android/src/main/res/values-de/strings.xml b/android/src/main/res/values-de/strings.xml new file mode 100644 index 000000000..bb8605bd6 --- /dev/null +++ b/android/src/main/res/values-de/strings.xml @@ -0,0 +1,20 @@ + + + Nicht gespeicherte Szenen + Hammer + Abbrechen + Speichern und schließen + Ungespeicherte Szenen speichern\? + Verwerfen und schließen + Laden… + Widget konfigurieren + Notiz hinzufügen + Mehr + Navigation + Speichern + Kategorien + Notiz hinzugefügen + Keine Projekte bisher + Notiz hinzufügen: + Notiz hinzugefügt + \ No newline at end of file diff --git a/android/src/main/res/values-es/strings.xml b/android/src/main/res/values-es/strings.xml new file mode 100644 index 000000000..ea48802d3 --- /dev/null +++ b/android/src/main/res/values-es/strings.xml @@ -0,0 +1,16 @@ + + + Hammer + Escenas sin guardar + Agregar nota + ¿Desea guardar las escenas sin guardar\? + Cargando… + Descartar y cerrar + Configurar Widget + Cancelar + Guardar y cerrar + Agregar nota a: + Guardar + Categorías + Agregar nota + \ No newline at end of file diff --git a/android/src/main/res/values-fr/strings.xml b/android/src/main/res/values-fr/strings.xml new file mode 100644 index 000000000..d4a35c2aa --- /dev/null +++ b/android/src/main/res/values-fr/strings.xml @@ -0,0 +1,20 @@ + + + Hammer + Scènes non enregistrées + Enregistrer des scènes non enregistrées \? + Ajouter une note + Chargement… + Configurer le widget + Rejeter et fermer + Annuler + Enregistrer et fermer + Plus de menu + Tiroir de navigation + Ajouter une note à : + Enregistrer + Catégories + Ajouter une note + Aucun projet + Note ajoutée + \ No newline at end of file diff --git a/android/src/main/res/values-nb-rNO/strings.xml b/android/src/main/res/values-nb-rNO/strings.xml new file mode 100644 index 000000000..e27271232 --- /dev/null +++ b/android/src/main/res/values-nb-rNO/strings.xml @@ -0,0 +1,4 @@ + + + Hammer + \ No newline at end of file diff --git a/android/src/main/res/values-uk/strings.xml b/android/src/main/res/values-uk/strings.xml new file mode 100644 index 000000000..06d1f0bd1 --- /dev/null +++ b/android/src/main/res/values-uk/strings.xml @@ -0,0 +1,20 @@ + + + Hammer + Додати нотатку + Завантаження… + Налаштувати віджет + Незбережені сцени + Зберегти незбережені сцени\? + Відхилити та закрити + Скасувати + Більше + Додати нотатку до: + Зберегти + Додати нотатку + Немає проєктів + Додано нотатку + Зберегти та закрити + Категорії + Навігаційна панель + \ No newline at end of file diff --git a/android/src/main/res/values-zh-rCN/strings.xml b/android/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 000000000..06c340e17 --- /dev/null +++ b/android/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,20 @@ + + + Hammer + 添加笔记 + 加载中… + 保存未保存的场景\? + 不保存退出 + 保存并退出 + 更多功能 + 导航抽屉 + 添加笔记至: + 未保存的场景 + 无项目 + 笔记已添加 + 配置小部件 + 取消 + 保存 + 分类 + 添加笔记 + \ No newline at end of file diff --git a/android/src/main/res/values/strings.xml b/android/src/main/res/values/strings.xml index 11cdab768..5bd0e9df3 100644 --- a/android/src/main/res/values/strings.xml +++ b/android/src/main/res/values/strings.xml @@ -2,6 +2,24 @@ Hammer Add Note - Loading... + Loading… Configure Widget + + Unsaved Scenes + Save unsaved scenes? + Discard and close + Cancel + Save and close + + More Menu + Navigation Drawer + + Add Note to: + Save + Categories + + Add Note + + No projects + Note Added \ No newline at end of file diff --git a/common/resources/MR/base/strings.xml b/common/resources/MR/base/strings.xml index 8711ff391..5bbf7878a 100644 --- a/common/resources/MR/base/strings.xml +++ b/common/resources/MR/base/strings.xml @@ -1,47 +1,4 @@ Hammer - - Projects: - No Projects Found - - Delete - - ⚙ Settings - UI Theme - Projects Directory - Path to Projects Directory - Select Dir - - Example Project - Install an example project to see how different features are - used. - - Reinstall - Example project created. - - Server - Setup a server for syncing between devices. - URL: - Email: - Setup - Modify - - Setup Server - HTTPS? - Server Url: myserver.com - E-Mail: user@example.com - Password - Login - Create - Cancel - - Delete Project - Are you sure you want to delete this project:\n%s - DELETE - Cancel - - Create Project - New Project Name - Create Project \ No newline at end of file diff --git a/common/resources/MR/base/strings_account_settings.xml b/common/resources/MR/base/strings_account_settings.xml new file mode 100644 index 000000000..6a5c5a9e9 --- /dev/null +++ b/common/resources/MR/base/strings_account_settings.xml @@ -0,0 +1,56 @@ + + + ⚙ Settings + UI Theme + Projects Directory + Path to Projects Directory + Select Dir + + Example Project + Install an example project to see how different features are + used. + + Reinstall + Example project created. + + Data: v%1$s + + Server + Setup a server for syncing between devices. + URL: + Email: + Setup + Modify + error + Auto-Sync + Backup on sync + Close Sync Dialogs on Success + Max Backups per Project + Test Auth + Re-Auth + Remove Auth + + Auth Test Successful + Auth Test Failed + + Remove Server + Are you sure you want to remove the server? + + Setup Server + Configure Server + HTTPS? + Server Url: myserver.com + E-Mail: user@example.com + Password + Show password + Hide password + Login + Create + Cancel + + Remove Local Content? + Should we remove your current local content before syncing? If not, we + will attempt to merge your local content with the server content. If the content is unrelated, it could cause + problems. + + \ No newline at end of file diff --git a/common/resources/MR/base/strings_drafts.xml b/common/resources/MR/base/strings_drafts.xml new file mode 100644 index 000000000..3e1f15046 --- /dev/null +++ b/common/resources/MR/base/strings_drafts.xml @@ -0,0 +1,18 @@ + + + Cancel + Merge with Current + Copy parts of the draft into here + Take Merged + Begin writing your Scene here + + Draft: %1$s + Copy desired parts into current, or accept all of this. + Take this whole draft + Draft + + Close Drafts + %1$s Drafts: + No Drafts Found + Created: %1$s + \ No newline at end of file diff --git a/common/resources/MR/base/strings_encyclopedia.xml b/common/resources/MR/base/strings_encyclopedia.xml new file mode 100644 index 000000000..1fd2ae70b --- /dev/null +++ b/common/resources/MR/base/strings_encyclopedia.xml @@ -0,0 +1,47 @@ + + + Search by Name + Clear + All + No Entries Found + Create Entry + + Create New Entry + Type: + Name + Tags (space seperated) + Describe your entry + Remove Image + Select Image + Create + Cancel + Entry Created + Entry Name was too long. Max %1$d + Entry Name must be alpha-numeric + Tag is too long. Max %1$d + Discard New Entry? + + Error: Failed to load entry! + Describe your entry + Click to enter description + + Cancel + Save + Entry Saved + + Discard Changes? + You will lose any changes you have made. + + Delete Entry? + This cannot be undone! + Entry Deleted + + Delete Image? + This cannot be undone! + + Name + Close Entry + + Discard Changes? + You will lose any changes you have made. + \ No newline at end of file diff --git a/common/resources/MR/base/strings_notes.xml b/common/resources/MR/base/strings_notes.xml new file mode 100644 index 000000000..bffdbe66c --- /dev/null +++ b/common/resources/MR/base/strings_notes.xml @@ -0,0 +1,21 @@ + + + Notes + No Notes Found + Create Note + Rename + Cancel + Delete + Discard Changes? + You will lose any changes you have made. + Create Note + New Note: + Create + Cancel + Note Created + Note was too long + Note was empty + Delete Note? + This action can not be un-done. + Note %1$s Deleted + \ No newline at end of file diff --git a/common/resources/MR/base/strings_project_home.xml b/common/resources/MR/base/strings_project_home.xml new file mode 100644 index 000000000..95004c9fb --- /dev/null +++ b/common/resources/MR/base/strings_project_home.xml @@ -0,0 +1,21 @@ + + + Stats: + Created: %1$s + Scenes + Total Words + Encyclopedia Entries + + Words in Chapters + Chapter + Words + + Actions: + Export Story + Sync Story + Backup + Backup Created: %1$s + Backup Failed + + Story Exported + \ No newline at end of file diff --git a/common/resources/MR/base/strings_project_navigation.xml b/common/resources/MR/base/strings_project_navigation.xml new file mode 100644 index 000000000..e1497d5ed --- /dev/null +++ b/common/resources/MR/base/strings_project_navigation.xml @@ -0,0 +1,8 @@ + + + Home + Editor + Notes + Encyclopedia + Time Line + \ No newline at end of file diff --git a/common/resources/MR/base/strings_project_select_navigation.xml b/common/resources/MR/base/strings_project_select_navigation.xml new file mode 100644 index 000000000..cf0177686 --- /dev/null +++ b/common/resources/MR/base/strings_project_select_navigation.xml @@ -0,0 +1,5 @@ + + + Projects + Settings + \ No newline at end of file diff --git a/common/resources/MR/base/strings_projects_list.xml b/common/resources/MR/base/strings_projects_list.xml new file mode 100644 index 000000000..7c03c0970 --- /dev/null +++ b/common/resources/MR/base/strings_projects_list.xml @@ -0,0 +1,36 @@ + + + Projects: + No Projects Found + + Delete + + Sync Projects + Create Project + Projects synced + Failed to sync projects + + Synchronization + Account Sync + Cancel + Sync Log + Pending + Error + Sync Complete + Canceled + + Delete Project + Are you sure you want to delete this project:\n%1$s + DELETE + Cancel + + Create Project + New Project Name + Create Project + + Project already exists + Missing Project Name + Project Name too long + Project Name contains invalid characters + Project Name was blank + \ No newline at end of file diff --git a/common/resources/MR/base/strings_scene_editor.xml b/common/resources/MR/base/strings_scene_editor.xml new file mode 100644 index 000000000..894e5e421 --- /dev/null +++ b/common/resources/MR/base/strings_scene_editor.xml @@ -0,0 +1,16 @@ + + + Save Draft: + Draft name + Save + Cancel + Draft Saved + + Scene Name + Rename + Cancel + Unsaved + Save + Saved + Begin writing your Scene here + \ No newline at end of file diff --git a/common/resources/MR/base/strings_scene_list.xml b/common/resources/MR/base/strings_scene_list.xml new file mode 100644 index 000000000..453326077 --- /dev/null +++ b/common/resources/MR/base/strings_scene_list.xml @@ -0,0 +1,33 @@ + + + Scenes: + Create Group + Create Scene + + Create Group + Group Name + + Create Scene + Scene Name + + Group Collapsed + Group Expanded + + Delete Scene + Are you sure you want to delete this scene: + "%1$s" + + DELETE + Dismiss + + Cannot Delete Group + You cannot delete a group while it still has children: + "%1$s" + + Okay + + Create + Cancel + + Cancel + \ No newline at end of file diff --git a/common/resources/MR/base/strings_sync.xml b/common/resources/MR/base/strings_sync.xml new file mode 100644 index 000000000..9690ba3fa --- /dev/null +++ b/common/resources/MR/base/strings_sync.xml @@ -0,0 +1,18 @@ + + + Conflict + Local Entry: + Use Local + + Remote Entry: + Use Remote + + Encyclopedia Entry Conflict + Local Event: %1$s + + Remote Event: %1$s + + Has Image + No Image + Encyclopedia Entry Conflict: + \ No newline at end of file diff --git a/common/resources/MR/base/strings_timeline.xml b/common/resources/MR/base/strings_timeline.xml new file mode 100644 index 000000000..040ac2622 --- /dev/null +++ b/common/resources/MR/base/strings_timeline.xml @@ -0,0 +1,24 @@ + + + Timeline + No Events + Create Event + + Close + Timeline Event + Date (optional) + Content + Create + Event Created + Failed to create event + + Save + Cancel + Close Entry + Date + Describe your event + Cancel + + Discard Changes? + You will lose any changes you have made. + \ No newline at end of file diff --git a/common/resources/MR/de/strings.xml b/common/resources/MR/de/strings.xml new file mode 100644 index 000000000..e27271232 --- /dev/null +++ b/common/resources/MR/de/strings.xml @@ -0,0 +1,4 @@ + + + Hammer + \ No newline at end of file diff --git a/common/resources/MR/de/strings_account_settings.xml b/common/resources/MR/de/strings_account_settings.xml new file mode 100644 index 000000000..ee7692826 --- /dev/null +++ b/common/resources/MR/de/strings_account_settings.xml @@ -0,0 +1,44 @@ + + + ⚙ Einstellungen + Schnittstellenfarbschema + Projektverzeichnis + Pfad zum Projektverzeichnis + E-Mail: + E-Mail: benutzer@beispiel.de + Sicherung bei Synchronisierung + Ordner wählen + Beispielprojekt + Beispielprojekt erstellt. + Daten: v%1$s + Bearbeiten + Server + URL: + Fehler + Automatische Synchronisierung + Neu installieren + Ein Beispielprojekt installieren, um zu sehen, wie verschiedene Funktionen verwendet werden. + Einrichten + Einrichtung eines Servers für die Synchronisierung mehrerer Geräte. + Server entfernen + Server konfigurieren + HTTPS aktivieren\? + Server Url: meinserver.de + Passwort + Passwort anzeigen + Passwort verbergen + Login + Abbrechen + Max Backups pro Projekt + Authentifizierung testen + Neu authentifizieren + Authentifizierung entfernen + Authentifizierungstest erfolgreich + Server einrichten + Synchronisierungsdialog bei Erfolg schließen + Erstellen + Lokale Inhalte entfernen\? + Authentifizierungstest fehlgeschlagen + Sicher, dass der Server entfernt werden soll\? + Sollen aktuelle lokale Inhalte vor der Synchronisierung entfernt werden\? Wenn nicht, wird versucht, die lokalen Inhalte mit Serverinhalten zusammenzuführen. Wenn die Inhalte nicht miteinander verknüpft sind, könnte dies zu Problemen führen. + \ No newline at end of file diff --git a/common/resources/MR/de/strings_drafts.xml b/common/resources/MR/de/strings_drafts.xml new file mode 100644 index 000000000..d094e2045 --- /dev/null +++ b/common/resources/MR/de/strings_drafts.xml @@ -0,0 +1,16 @@ + + + Entwurf schließen + %1$s Entwürfe: + Abbrechen + Entwurf: %1$s + Entwurf + Keine Entwürfe gefunden + Erstellt: %1$s + Hier mit dem Schreiben der Szene beginnen + Teile des Entwurfs hier her kopieren + Take zusammengeführt + Kopieren der gewünschten Teile in den aktuellen Text, oder alles übernehmen. + Den ganzen Entwurf übernehmen + Mit aktuellem Text zusammenführen + \ No newline at end of file diff --git a/common/resources/MR/de/strings_encyclopedia.xml b/common/resources/MR/de/strings_encyclopedia.xml new file mode 100644 index 000000000..af0e43e72 --- /dev/null +++ b/common/resources/MR/de/strings_encyclopedia.xml @@ -0,0 +1,37 @@ + + + Alle + Keine Einträge gefunden + Eintrag erstellen + Neuen Eintrag erstellen + Typ: + Name + Schlagworte (Leerzeichen separiert) + Eintrag beschreiben + Bild entfernen + Bild auswählen + Erstellen + Abbrechen + Eintrag erstellt + Name des Eintrags war zu lang (Max: %1$d) + Es dürfen nur alphanumerische Zeichen verwendet werden. + Schlagwort ist zu lang (Max: %1$d) + Neuen Eintrag verwerfen\? + Fehler: Eintrag konnte nicht geladen werden! + Eintrag beschreiben + Nach Name suchen + Zurücksetzen + Klicken um Beschreibung einzugeben + Abbrechen + Speichern + Eintrag gespeichert + Änderungen verwerfen\? + Alle Änderungen werden verloren gehen. + Eintrag löschen\? + Eintrag schließen + Dies kann nicht rückgängig gemacht werden! + Dies kann nicht rückgängig gemacht werden! + Eintrag gelöscht + Bild löschen\? + Name + \ No newline at end of file diff --git a/common/resources/MR/de/strings_notes.xml b/common/resources/MR/de/strings_notes.xml new file mode 100644 index 000000000..55724e02b --- /dev/null +++ b/common/resources/MR/de/strings_notes.xml @@ -0,0 +1,21 @@ + + + Notizen + Keine Notizen gefunden + Notiz erstellen + Umbenennen + Abbrechen + Löschen + Notiz erstellen + Änderungen verwerfen\? + Neue Notiz: + Erstellen + Abbrechen + Notiz erstellt + Notiz ist leer + Notiz löschen\? + Notiz kann nicht wiederhergestellt werden! + Notiz %1$s gelöscht + Alle Änderungen werden verloren gehen. + Notiz war zu lang + \ No newline at end of file diff --git a/common/resources/MR/de/strings_project_home.xml b/common/resources/MR/de/strings_project_home.xml new file mode 100644 index 000000000..bda479da6 --- /dev/null +++ b/common/resources/MR/de/strings_project_home.xml @@ -0,0 +1,17 @@ + + + Erstellt: %1$s + Szenen + Einträge der Enzyklopädie + Kapitel + Wörter + Geschichte exportieren + Geschichte synchronisieren + Backup + Backup erstellt: %1$s + Backup fehlgeschlagen + Statistik: + Wörter Gesamt + Wörter in Kapiteln + Aktionen: + \ No newline at end of file diff --git a/common/resources/MR/de/strings_projects_list.xml b/common/resources/MR/de/strings_projects_list.xml new file mode 100644 index 000000000..e87df634e --- /dev/null +++ b/common/resources/MR/de/strings_projects_list.xml @@ -0,0 +1,26 @@ + + + Projekte: + Keine Projekte gefunden + Löschen + Projekte synchronisieren + Projekt erstellen + Projekte synchronisiert + Projektsynchronisierung fehlgeschlagen + Synchronisierung + Konto Synchronisierung + Abbrechen + Synchronisierungslog + Fehler + Synchronisierung abgeschlossen + Abgebrochen + Projekt löschen + LÖSCHEN + Abbrechen + Projekt erstellen + Projektname + Sicher, dass folgendes Projekt gelöscht werden soll\?: +\n%1$s + Projekt erstellen + Ausstehend + \ No newline at end of file diff --git a/common/resources/MR/de/strings_scene_editor.xml b/common/resources/MR/de/strings_scene_editor.xml new file mode 100644 index 000000000..796b58da3 --- /dev/null +++ b/common/resources/MR/de/strings_scene_editor.xml @@ -0,0 +1,15 @@ + + + Entwurf speichern: + Speichern + Abbrechen + Entwurf gespeichert + Name der Szene + Umbennen + Abbrechen + Nicht gespeichert + Speichern + Gespeichert + Hier mit dem Schreiben der Szene beginnen + Name des Entwurfs + \ No newline at end of file diff --git a/common/resources/MR/de/strings_scene_list.xml b/common/resources/MR/de/strings_scene_list.xml new file mode 100644 index 000000000..96e397b7c --- /dev/null +++ b/common/resources/MR/de/strings_scene_list.xml @@ -0,0 +1,19 @@ + + + Gruppe erstellen + Szene erstellen + Gruppe erstellen + Szenen: + Name der Gruppe + Szene erstellen + Name der Szene + Szene löschen + LÖSCHEN + Verwerfen + Gruppe kann nicht gelöscht werden + Erstellen + Abbrechen + Gruppe eingeklappt + Gruppe ausgeklappt + Sicher, dass diese Szene gelöscht werden soll\?: %1$s + \ No newline at end of file diff --git a/common/resources/MR/es/strings.xml b/common/resources/MR/es/strings.xml new file mode 100644 index 000000000..e27271232 --- /dev/null +++ b/common/resources/MR/es/strings.xml @@ -0,0 +1,4 @@ + + + Hammer + \ No newline at end of file diff --git a/common/resources/MR/es/strings_account_settings.xml b/common/resources/MR/es/strings_account_settings.xml new file mode 100644 index 000000000..e74c8b256 --- /dev/null +++ b/common/resources/MR/es/strings_account_settings.xml @@ -0,0 +1,13 @@ + + + Correo electrónico: + Configuración + Tema UI + Instala un proyecto de ejemplo para ver cómo se usan las diferentes funciones. + Reinstalar + Servidor + URL: + Modificar + error + Seleccionar directorio + \ No newline at end of file diff --git a/common/resources/MR/es/strings_drafts.xml b/common/resources/MR/es/strings_drafts.xml new file mode 100644 index 000000000..6f93d3062 --- /dev/null +++ b/common/resources/MR/es/strings_drafts.xml @@ -0,0 +1,10 @@ + + + Cancelar + Comienza escribiendo tu escena aquí + Borrador + Cerrar borrador + Ningún borrador encontrado + Copia partes del borrador aquí + Borrador: %1$s + \ No newline at end of file diff --git a/common/resources/MR/es/strings_encyclopedia.xml b/common/resources/MR/es/strings_encyclopedia.xml new file mode 100644 index 000000000..5ffd5f1d2 --- /dev/null +++ b/common/resources/MR/es/strings_encyclopedia.xml @@ -0,0 +1,33 @@ + + + Buscar por nombre + Borrar + Crear entrada + Crear nueva entrada + Nombre + Etiquetas (espacios separados) + Describe tu entrada + Remover imagen + Seleccionar imagen + Crear + Cancelar + Entrada creada + Describe tu entrada + Cliquea para completar descripción + El nombre de entrada es muy largo. Máximo %1$d + El nombre de la entrada debe ser alfanumérico + La etiqueta es muy larga. Máximo %1$d + ¿Descartar nueva entrada\? + Cancelar + Guardar + Entrada guardada + ¿Descartar cambios\? + Vas a perder cualquier cambio que hayas realizado. + ¿Eliminar entrada\? + ¡Esto no se puede deshacer! + Entrada eliminada + ¿Eliminar imagen\? + ¡Esto no se puede deshacer! + Nombre + Cerrar entrada + \ No newline at end of file diff --git a/common/resources/MR/es/strings_notes.xml b/common/resources/MR/es/strings_notes.xml new file mode 100644 index 000000000..ccb130637 --- /dev/null +++ b/common/resources/MR/es/strings_notes.xml @@ -0,0 +1,18 @@ + + + Notas + No se encontraron notas + Crear nota + Renombrar + Cancelar + Eliminar + ¿Descartar cambios\? + Vas a perder cualquier cambio que hayas hecho. + Crear nota + Nota nueva: + Crear + Cancelar + Nota creada + ¿Eliminar nota\? + Esta acción no se puede deshacer. + \ No newline at end of file diff --git a/common/resources/MR/es/strings_project_home.xml b/common/resources/MR/es/strings_project_home.xml new file mode 100644 index 000000000..0507763d4 --- /dev/null +++ b/common/resources/MR/es/strings_project_home.xml @@ -0,0 +1,13 @@ + + + Estadísticas: + Escenas + Total de palabras + Palabras por capítulo + Capítulo + Palabras + Acciones: + Exportar historia + Respaldo + Entradas de la enciclopedia + \ No newline at end of file diff --git a/common/resources/MR/es/strings_projects_list.xml b/common/resources/MR/es/strings_projects_list.xml new file mode 100644 index 000000000..ea523a8ff --- /dev/null +++ b/common/resources/MR/es/strings_projects_list.xml @@ -0,0 +1,14 @@ + + + Proyectos: + No se encontraron proyectos + Eliminar + Crear proyexto + Sincronización + Pendiente + Error + Proyectos sincronizados + Cancelado + Eliminar proyecto + Cancelar + \ No newline at end of file diff --git a/common/resources/MR/es/strings_scene_editor.xml b/common/resources/MR/es/strings_scene_editor.xml new file mode 100644 index 000000000..5f8f3bae6 --- /dev/null +++ b/common/resources/MR/es/strings_scene_editor.xml @@ -0,0 +1,14 @@ + + + Guardar borrador: + Nombre del borrador + Guardar + Cancelar + Borrador guardado + Nombre de la escena + Renombrar + Cancelar + Sin guardar + Guardar + Guardado + \ No newline at end of file diff --git a/common/resources/MR/es/strings_scene_list.xml b/common/resources/MR/es/strings_scene_list.xml new file mode 100644 index 000000000..a1da1ef17 --- /dev/null +++ b/common/resources/MR/es/strings_scene_list.xml @@ -0,0 +1,13 @@ + + + Escenas: + Crear grupo + Crear escena + Crear grupo + Nombre del grupo + Crear escena + Nombre de escena + Borrar escena + ELIMINAR + Crear + \ No newline at end of file diff --git a/common/resources/MR/fr/strings.xml b/common/resources/MR/fr/strings.xml new file mode 100644 index 000000000..e27271232 --- /dev/null +++ b/common/resources/MR/fr/strings.xml @@ -0,0 +1,4 @@ + + + Hammer + \ No newline at end of file diff --git a/common/resources/MR/fr/strings_account_settings.xml b/common/resources/MR/fr/strings_account_settings.xml new file mode 100644 index 000000000..2944cb394 --- /dev/null +++ b/common/resources/MR/fr/strings_account_settings.xml @@ -0,0 +1,44 @@ + + + ⚙ Paramètres + Thème de l’interface utilisateur + Répertoire des projets + Sélectionnez rép + Exemple de projet + Installez un exemple de projet pour voir comment les différentes fonctionnalités sont utilisées. + Réinstaller + Exemple de projet créé. + Données : v%1$s + Serveur + Configurer un serveur pour la synchronisation entre les appareils. + URL : + Courriel : + Configurer + Modifier + erreur + Synchro auto + Sauvegarder en synchronisant + Fermer les dialogues de synchronisation en cas de succès + Nombre maximal de sauvegardes par projet + Test d’authentification + Se réauthentifier + Retirer l’authentification + Test d’authentification réussi + Échec du test d’authentification + Retirer le serveur + Êtes-vous sûr·e de vouloir supprimer le serveur \? + HTTPS \? + URL du serveur : monserveur.com + Courriel : utilisateur@exemple.com + Afficher le mot de passe + Masquer le mot de passe + Se connecter + Créer + Annuler + Retirer le contenu local \? + Chemin d’accès au répertoire des projets + Configurer le serveur + Mot de passe + Configurer le serveur + Devons-nous retirer votre contenu local actuel avant de procéder à la synchronisation \? Si ce n’est pas le cas, nous tenterons de fusionner votre contenu local avec le contenu du serveur. Si le contenu n’est pas lié, cela peut poser des problèmes. + \ No newline at end of file diff --git a/common/resources/MR/fr/strings_drafts.xml b/common/resources/MR/fr/strings_drafts.xml new file mode 100644 index 000000000..3198f18d6 --- /dev/null +++ b/common/resources/MR/fr/strings_drafts.xml @@ -0,0 +1,15 @@ + + + Annuler + Fusionner avec l’actuel + Copier des parties du projet ici + Brouillon: %1$s + Copier les parties souhaitées dans la version actuelle, ou accepter l’ensemble. + Prenez l’ensemble de ce brouillon + Brouillon + Fermer les brouillons + %1$s brouillons: + Commencez à écrire votre Scène ici + Aucun brouillon trouvé + Créé: %1$s + \ No newline at end of file diff --git a/common/resources/MR/fr/strings_encyclopedia.xml b/common/resources/MR/fr/strings_encyclopedia.xml new file mode 100644 index 000000000..3d31524bc --- /dev/null +++ b/common/resources/MR/fr/strings_encyclopedia.xml @@ -0,0 +1,37 @@ + + + Effacer + Aucune entrée trouvée + Créer une entrée + Type : + Nom + Étiquettes (séparés par des espaces) + Décrivez votre entrée + Retirer l’image + Sélectionner une image + Créer + Annuler + Entrée créée + L’étiquette est trop longue. Max %1$d + Supprimer la nouvelle entrée \? + Erreur : Échec du chargement de l’entrée ! + Décrivez votre entrée + Enregistrer + Entrée enregistrée + Supprimer les modifications \? + Vous perdrez toutes les modifications que vous avez faites. + Supprimer l’entrée \? + Ceci est irréversible ! + Entrée supprimée + Supprimer l’image \? + Ceci est irréversible ! + Nom + Fermer l’entrée + Tout + Rechercher par nom + Créer une nouvelle entrée + Le nom de l’entrée est trop long. Max %1$d + Le nom de l’entrée doit être alphanumérique + Cliquez pour saisir la description + Annuler + \ No newline at end of file diff --git a/common/resources/MR/fr/strings_notes.xml b/common/resources/MR/fr/strings_notes.xml new file mode 100644 index 000000000..e6f9370f9 --- /dev/null +++ b/common/resources/MR/fr/strings_notes.xml @@ -0,0 +1,21 @@ + + + Notes + Aucune note trouvée + Renommer + Annuler + Supprimer + Vous perdrez toutes les modifications que vous avez faites. + Créer une note + Nouvelle note : + Créer + Annuler + Annuler les modifications \? + Note créée + La note était trop longue + La note était vide + Supprimer la note \? + Cette action ne peut pas être annulée. + Note %1$s supprimée + Créer une note + \ No newline at end of file diff --git a/common/resources/MR/fr/strings_project_home.xml b/common/resources/MR/fr/strings_project_home.xml new file mode 100644 index 000000000..17eb2298c --- /dev/null +++ b/common/resources/MR/fr/strings_project_home.xml @@ -0,0 +1,17 @@ + + + Statistiques : + Créé : %1$s + Scènes + Nombre de mots + Entrées de l’encyclopédie + Mots dans les chapitres + Chapitre + Mots + Actions : + Exporter l\'histoire + Synchroniser l’histoire + Sauvegarde + Sauvegarde créée : %1$s + Échec de la sauvegarde + \ No newline at end of file diff --git a/common/resources/MR/fr/strings_projects_list.xml b/common/resources/MR/fr/strings_projects_list.xml new file mode 100644 index 000000000..ff19d8b50 --- /dev/null +++ b/common/resources/MR/fr/strings_projects_list.xml @@ -0,0 +1,26 @@ + + + SUPPRIMER + Annuler + Nouveau nom du projet + Créer un projet + Projets : + Aucun projet trouvé + Supprimer + Synchroniser les projets + Créer un projet + Projets synchronisés + Échec de la synchronisation des projets + Synchronisation + Synchronisation des comptes + Annuler + Journal de synchronisation + En attente + Erreur + Synchronisation terminée + Annulé + Supprimer le projet + Êtes-vous sûr·e de vouloir supprimer ce projet \? +\n%1$s + Créer un projet + \ No newline at end of file diff --git a/common/resources/MR/fr/strings_scene_editor.xml b/common/resources/MR/fr/strings_scene_editor.xml new file mode 100644 index 000000000..cb8252c06 --- /dev/null +++ b/common/resources/MR/fr/strings_scene_editor.xml @@ -0,0 +1,15 @@ + + + Enregistrer le brouillon : + Nom du brouillon + Enregistrer + Annuler + Brouillon enregistré + Nom de la scène + Renommer + Annuler + Non enregistré + Enregistrer + Enregistré + Commencez à écrire votre Scène ici + \ No newline at end of file diff --git a/common/resources/MR/fr/strings_scene_list.xml b/common/resources/MR/fr/strings_scene_list.xml new file mode 100644 index 000000000..6c759f37d --- /dev/null +++ b/common/resources/MR/fr/strings_scene_list.xml @@ -0,0 +1,19 @@ + + + Scènes : + Créer un groupe + Créer une scène + SUPPRIMER + Rejeter + Impossible de supprimer le groupe + Créer + Annuler + Créer un groupe + Nom du groupe + Créer une scène + Nom de la scène + Groupe réduit + Groupe développé + Supprimer la scène + Êtes-vous sûr·e de vouloir supprimer cette scène \? %1$s + \ No newline at end of file diff --git a/common/resources/MR/nb-rNO/strings.xml b/common/resources/MR/nb-rNO/strings.xml new file mode 100644 index 000000000..e27271232 --- /dev/null +++ b/common/resources/MR/nb-rNO/strings.xml @@ -0,0 +1,4 @@ + + + Hammer + \ No newline at end of file diff --git a/common/resources/MR/nb-rNO/strings_account_settings.xml b/common/resources/MR/nb-rNO/strings_account_settings.xml new file mode 100644 index 000000000..0d2c4cc40 --- /dev/null +++ b/common/resources/MR/nb-rNO/strings_account_settings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/common/resources/MR/nb-rNO/strings_drafts.xml b/common/resources/MR/nb-rNO/strings_drafts.xml new file mode 100644 index 000000000..0d2c4cc40 --- /dev/null +++ b/common/resources/MR/nb-rNO/strings_drafts.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/common/resources/MR/nb-rNO/strings_encyclopedia.xml b/common/resources/MR/nb-rNO/strings_encyclopedia.xml new file mode 100644 index 000000000..0d2c4cc40 --- /dev/null +++ b/common/resources/MR/nb-rNO/strings_encyclopedia.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/common/resources/MR/nb-rNO/strings_notes.xml b/common/resources/MR/nb-rNO/strings_notes.xml new file mode 100644 index 000000000..0d2c4cc40 --- /dev/null +++ b/common/resources/MR/nb-rNO/strings_notes.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/common/resources/MR/nb-rNO/strings_project_home.xml b/common/resources/MR/nb-rNO/strings_project_home.xml new file mode 100644 index 000000000..0d2c4cc40 --- /dev/null +++ b/common/resources/MR/nb-rNO/strings_project_home.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/common/resources/MR/nb-rNO/strings_projects_list.xml b/common/resources/MR/nb-rNO/strings_projects_list.xml new file mode 100644 index 000000000..0d2c4cc40 --- /dev/null +++ b/common/resources/MR/nb-rNO/strings_projects_list.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/common/resources/MR/nb-rNO/strings_scene_editor.xml b/common/resources/MR/nb-rNO/strings_scene_editor.xml new file mode 100644 index 000000000..0d2c4cc40 --- /dev/null +++ b/common/resources/MR/nb-rNO/strings_scene_editor.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/common/resources/MR/nb-rNO/strings_scene_list.xml b/common/resources/MR/nb-rNO/strings_scene_list.xml new file mode 100644 index 000000000..0d2c4cc40 --- /dev/null +++ b/common/resources/MR/nb-rNO/strings_scene_list.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/common/resources/MR/template.xml b/common/resources/MR/template.xml new file mode 100644 index 000000000..998fc07cb --- /dev/null +++ b/common/resources/MR/template.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/common/resources/MR/uk/strings.xml b/common/resources/MR/uk/strings.xml new file mode 100644 index 000000000..e27271232 --- /dev/null +++ b/common/resources/MR/uk/strings.xml @@ -0,0 +1,4 @@ + + + Hammer + \ No newline at end of file diff --git a/common/resources/MR/uk/strings_account_settings.xml b/common/resources/MR/uk/strings_account_settings.xml new file mode 100644 index 000000000..3d4b7c293 --- /dev/null +++ b/common/resources/MR/uk/strings_account_settings.xml @@ -0,0 +1,44 @@ + + + Приклад проєкту + Встановіть приклад проєкту, щоб побачити, як використовуються різні функції. + Перевстановлення + Створено приклад проєкту. + Дані: v%1$s + Сервер + Налаштуйте сервер для синхронізації між пристроями. + URL: + Пошта: + Встановити + Змінити + помилка + Автоматична синхронізація + Резервне копіювання при синхронізації + Закрити діалогові вікна синхронізації після успіху + Максимальна кількість резервних копій на проєкт + Повторна авторизація + Тест Авторизації + Видалити авторизацію + Тест авторизації пройдено успішно + Видалити сервер + Ви впевнені, що хочете видалити сервер\? + HTTPS\? + Url серверу : myserver.com + Пошта: user@example.com + Пароль + Показати пароль + Приховати пароль + Логін + Створити + Скасувати + Видалити локальний вміст\? + Чи потрібно видалити поточний локальний вміст перед синхронізацією\? Якщо ні, ми спробуємо об\'єднати ваш локальний вміст із вмістом на сервері. Якщо вміст не пов\'язаний між собою, це може спричинити проблеми. + Налаштування сервера + Тест авторизації не пройдено + Налаштувати сервер + ⚙ Налаштування + Тема інтерфейсу + Каталог проєктів + Шлях до каталогу проєктів + Вибрати каталог + \ No newline at end of file diff --git a/common/resources/MR/uk/strings_drafts.xml b/common/resources/MR/uk/strings_drafts.xml new file mode 100644 index 000000000..84fd3ed9b --- /dev/null +++ b/common/resources/MR/uk/strings_drafts.xml @@ -0,0 +1,16 @@ + + + Скасувати + Об\'єднати з поточним текстом + Взяти об\'єднані + Почніть писати свою Сцену тут + Скопіюйте частини чернетки сюди + Чернетка: %1$s + Скопіюйте потрібні частини в поточний текст або візьміть на себе весь текст. + Візьміть всю цю чернетку + Чернетка + Закрити чернетки + %1$s Чернеток: + Чернеток не знайдено + Створено: %1$s + \ No newline at end of file diff --git a/common/resources/MR/uk/strings_encyclopedia.xml b/common/resources/MR/uk/strings_encyclopedia.xml new file mode 100644 index 000000000..b6522d8bc --- /dev/null +++ b/common/resources/MR/uk/strings_encyclopedia.xml @@ -0,0 +1,37 @@ + + + Запис створено + Назва запису була занадто довгою. Макс %1$d + Назва запису має бути буквено-цифровою + Пошук за назвою + Очистити + Всі + Записів не знайдено + Створити запис + Створити новий запис + Тип: + Ім’я + Теги (через пробіл) + Опишіть свій запис + Видалити зображення + Створити + Вибрати зображення + Скасувати + Тег занадто довгий. Макс %1$d + Відхилити новий запис\? + Помилка: Не вдалося завантажити запис! + Опишіть свій запис + Натисніть, щоб ввести опис + Скасувати + Зберегти + Запис збережено + Відхилити зміни\? + Ви втратите всі внесені зміни. + Видалити запис\? + Цього не можна скасувати! + Запис видалено + Видалити зображення\? + Це неможливо скасувати! + Назва + Закрити запис + \ No newline at end of file diff --git a/common/resources/MR/uk/strings_notes.xml b/common/resources/MR/uk/strings_notes.xml new file mode 100644 index 000000000..2990dec9a --- /dev/null +++ b/common/resources/MR/uk/strings_notes.xml @@ -0,0 +1,21 @@ + + + Нотатки + Нотаток не знайдено + Створити нотатку + Переіменувати + Скасувати + Видалити + Відхилити зміни\? + Ви втратите всі внесені зміни. + Створити нотатку + Нова нотатка: + Створити + Скасувати + Нотатку створено + Нотатка була занадто довгою + Нотатка була порожня + Видалити нотатку\? + Цю дію неможливо скасувати. + Примітка %1$s видалено + \ No newline at end of file diff --git a/common/resources/MR/uk/strings_project_home.xml b/common/resources/MR/uk/strings_project_home.xml new file mode 100644 index 000000000..4a5c1135e --- /dev/null +++ b/common/resources/MR/uk/strings_project_home.xml @@ -0,0 +1,17 @@ + + + Резервне копіювання + Статистика: + Створено: %1$s + Сцени + Всього слів + Статті в енциклопедії + Розділ + Слів у розділах + Слова + Дії: + Історія експорту + Історія синхронізації + Резервну копію створено: %1$s + Помилка резервного копіювання + \ No newline at end of file diff --git a/common/resources/MR/uk/strings_projects_list.xml b/common/resources/MR/uk/strings_projects_list.xml new file mode 100644 index 000000000..0fd861634 --- /dev/null +++ b/common/resources/MR/uk/strings_projects_list.xml @@ -0,0 +1,26 @@ + + + Проєкти: + Проєктів не знайдено + Видалити + Синхронізація проєктів + Створити проєкт + Проєкти синхронізовано + Не вдалося синхронізувати проєкти + Синхронізація + Синхронізація облікового запису + Скасувати + Журнал синхронізації + В очікуванні + Помилка + Синхронізацію завершено + Скасовано + Видалити проєкт + Ви впевнені, що хочете видалити цей проєкт\? +\n%1$s + ВИДАЛИТИ + Скасувати + Створити проєкт + Нова назва проєкту + Створити проєкт + \ No newline at end of file diff --git a/common/resources/MR/uk/strings_scene_editor.xml b/common/resources/MR/uk/strings_scene_editor.xml new file mode 100644 index 000000000..292d1a107 --- /dev/null +++ b/common/resources/MR/uk/strings_scene_editor.xml @@ -0,0 +1,15 @@ + + + Скасувати + Чернетку збережено + Незбережені + Назва сцени + Скасувати + Зберегти + Перейменувати + Збережено + Почніть писати свою сцену тут + Зберегти + Зберегти чернетку: + Назва чернетки + \ No newline at end of file diff --git a/common/resources/MR/uk/strings_scene_list.xml b/common/resources/MR/uk/strings_scene_list.xml new file mode 100644 index 000000000..8acf4db76 --- /dev/null +++ b/common/resources/MR/uk/strings_scene_list.xml @@ -0,0 +1,19 @@ + + + Сцени: + Створити групу + Створити сцену + Створити групу + Назва групи + Створити сцену + Назва сцени + Групу згорнуто + Групу розширено + Видалити сцену + Ви впевнені, що хочете видалити цю сцену\? %1$s + ВИДАЛИТИ + Створити + Скасувати + Скасувати + Не вдається видалити групу + \ No newline at end of file diff --git a/common/resources/MR/zh-rCN/strings.xml b/common/resources/MR/zh-rCN/strings.xml new file mode 100644 index 000000000..e27271232 --- /dev/null +++ b/common/resources/MR/zh-rCN/strings.xml @@ -0,0 +1,4 @@ + + + Hammer + \ No newline at end of file diff --git a/common/resources/MR/zh-rCN/strings_account_settings.xml b/common/resources/MR/zh-rCN/strings_account_settings.xml new file mode 100644 index 000000000..eed43017f --- /dev/null +++ b/common/resources/MR/zh-rCN/strings_account_settings.xml @@ -0,0 +1,44 @@ + + + 设置 + 界面设置 + 项目示例 + 选择文件夹 + 重新获取 + 获取一个示例项目以了解如何使用不同的功能. + 已新建示例项目. + 项目文件夹路径 + 修改 + 数据: v%1$s + 服务器 + 设置一个服务器以在不同设备间同步. + URL: + 在成功后关闭同步对话框 + 邮箱: + 配置 + 验证 + 重新验证 + 验证失败 + 配置服务器 + 自动同步 + 同步备份 + 错误 + 每个项目的最大备份数 + 移除验证 + 验证成功 + 移除服务器 + 确定要移除此服务器吗\? + 项目文件夹 + 编辑服务器配置 + 启用HTTPS\? + 登陆 + 服务器URL: myserver.com + 邮箱: user@example.com + 密码 + 显示密码 + 隐藏密码 + 创建 + 取消 + 移除本地内容\? + 是否在同步之前移除当前本地的内容\? 取消后, 将尝试把本地内容与服务器内容合并, 这可能会导致问题。 + \ No newline at end of file diff --git a/common/resources/MR/zh-rCN/strings_drafts.xml b/common/resources/MR/zh-rCN/strings_drafts.xml new file mode 100644 index 000000000..5181336c4 --- /dev/null +++ b/common/resources/MR/zh-rCN/strings_drafts.xml @@ -0,0 +1,16 @@ + + + 取消 + 合并 + 在此撰写场景 + 与当前内容合并 + 将草稿的部分复制到此处 + 保存整个草稿 + 关闭草稿 + %1$s 草稿: + 没有找到草稿 + 已创建: %1$s + 草稿: %1$s + 将所需的部分或全部内容复制到此处. + 草稿 + \ No newline at end of file diff --git a/common/resources/MR/zh-rCN/strings_encyclopedia.xml b/common/resources/MR/zh-rCN/strings_encyclopedia.xml new file mode 100644 index 000000000..5d222ac37 --- /dev/null +++ b/common/resources/MR/zh-rCN/strings_encyclopedia.xml @@ -0,0 +1,37 @@ + + + 清除 + 通过名称搜索 + 点击此处描述 + 全部 + 未找到条目 + 类型: + 舍弃新条目\? + 错误: 条目加载失败! + 新建条目 + 描述此条目 + 创建新的条目 + 取消 + 名称 + 标签 (以空格分隔) + 保存 + 描述此条目 + 条目已保存 + 删除条目\? + 移除图片 + 舍弃编辑\? + 将失去所有已编辑的内容. + 选择图片 + 条目名过长. 最大限制: %1$d + 新建 + 已创建条目 + 取消 + 条目名称必须是字母数字 + 标签过长. 最大限制 %1$d + 关闭条目 + 此操作无法撤销! + 删除图片\? + 条目已删除 + 此操作无法撤销! + 名称 + \ No newline at end of file diff --git a/common/resources/MR/zh-rCN/strings_notes.xml b/common/resources/MR/zh-rCN/strings_notes.xml new file mode 100644 index 000000000..a34b17e3d --- /dev/null +++ b/common/resources/MR/zh-rCN/strings_notes.xml @@ -0,0 +1,21 @@ + + + 笔记 + 未找到笔记 + 新建编辑 + 已新建笔记 + 编辑过长 + 笔记是空的 + 删除笔记\? + 此操作无法撤销. + 删除笔记 + 重命名 + 将失去所有已编辑的内容. + 取消 + 舍弃编辑\? + 笔记 %1$s 已删除 + 新建笔记 + 新笔记: + 新建 + 取消 + \ No newline at end of file diff --git a/common/resources/MR/zh-rCN/strings_project_home.xml b/common/resources/MR/zh-rCN/strings_project_home.xml new file mode 100644 index 000000000..373b0e24d --- /dev/null +++ b/common/resources/MR/zh-rCN/strings_project_home.xml @@ -0,0 +1,17 @@ + + + 条目分布 + 各章节词数 + 统计: + 已创建: %1$s + 场景 + 字数总计 + 已创建备份: %1$s + 备份 + 章节 + 导出故事 + 操作: + 词数 + 备份失败 + 同步故事 + \ No newline at end of file diff --git a/common/resources/MR/zh-rCN/strings_projects_list.xml b/common/resources/MR/zh-rCN/strings_projects_list.xml new file mode 100644 index 000000000..5d03724e5 --- /dev/null +++ b/common/resources/MR/zh-rCN/strings_projects_list.xml @@ -0,0 +1,26 @@ + + + 新建项目 + 新建项目 + 项目: + 取消 + 删除项目 + 项目已同步 + 账号同步 + 取消 + 同步项目 + 同步 + 同步日志 + 接收中 + 同步错误 + 同步完成 + 已取消 + 确定删除此项目: +\n%1$s + 删除 + 新建项目名称 + 新建项目 + 确认删除项目 + 未找到项目 + 项目同步失败 + \ No newline at end of file diff --git a/common/resources/MR/zh-rCN/strings_scene_editor.xml b/common/resources/MR/zh-rCN/strings_scene_editor.xml new file mode 100644 index 000000000..bd80bf9eb --- /dev/null +++ b/common/resources/MR/zh-rCN/strings_scene_editor.xml @@ -0,0 +1,15 @@ + + + 保存草稿: + 草稿名 + 保存 + 草稿已保存 + 场景名 + 重命名 + 取消 + 不保存 + 保存 + 已保存 + 在此撰写场景 + 取消 + \ No newline at end of file diff --git a/common/resources/MR/zh-rCN/strings_scene_list.xml b/common/resources/MR/zh-rCN/strings_scene_list.xml new file mode 100644 index 000000000..332bcf286 --- /dev/null +++ b/common/resources/MR/zh-rCN/strings_scene_list.xml @@ -0,0 +1,19 @@ + + + 新建分组 + 新建分组 + 场景名 + 分组已折叠 + 分组已展开 + 删除场景 + 取消 + 分组名 + 确定要删除此场景: %1$s + 新建场景 + 删除 + 新建 + 场景: + 新建场景 + 无法删除分组 + 关闭 + \ No newline at end of file diff --git a/common/src/androidMain/kotlin/com/darkrockstudios/apps/hammer/common/dependencyinjection/platformModule.kt b/common/src/androidMain/kotlin/com/darkrockstudios/apps/hammer/common/dependencyinjection/platformModule.kt index c97520fd0..7af4239c4 100644 --- a/common/src/androidMain/kotlin/com/darkrockstudios/apps/hammer/common/dependencyinjection/platformModule.kt +++ b/common/src/androidMain/kotlin/com/darkrockstudios/apps/hammer/common/dependencyinjection/platformModule.kt @@ -1,9 +1,11 @@ package com.darkrockstudios.apps.hammer.common.dependencyinjection import com.darkrockstudios.apps.hammer.common.util.NetworkConnectivity +import com.darkrockstudios.apps.hammer.common.util.StrRes import org.koin.core.module.dsl.singleOf import org.koin.dsl.module actual val platformModule = module { singleOf(::NetworkConnectivity) + singleOf(::StrRes) } \ No newline at end of file diff --git a/common/src/androidMain/kotlin/com/darkrockstudios/apps/hammer/common/util/MokoUtils.kt b/common/src/androidMain/kotlin/com/darkrockstudios/apps/hammer/common/util/MokoUtils.kt new file mode 100644 index 000000000..b2d88d676 --- /dev/null +++ b/common/src/androidMain/kotlin/com/darkrockstudios/apps/hammer/common/util/MokoUtils.kt @@ -0,0 +1,11 @@ +package com.darkrockstudios.apps.hammer.common.util + +import android.content.Context +import dev.icerock.moko.resources.StringResource + +actual class StrRes(private val context: Context) { + private val res = context.resources + + actual fun get(str: StringResource) = res.getString(str.resourceId) + actual fun get(str: StringResource, vararg args: Any) = res.getString(str.resourceId, args) +} \ No newline at end of file diff --git a/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/components/projecteditor/sceneeditor/SceneEditor.kt b/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/components/projecteditor/sceneeditor/SceneEditor.kt index fabf6fb69..840c21df3 100644 --- a/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/components/projecteditor/sceneeditor/SceneEditor.kt +++ b/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/components/projecteditor/sceneeditor/SceneEditor.kt @@ -6,6 +6,7 @@ import com.darkrockstudios.apps.hammer.common.data.PlatformRichText import com.darkrockstudios.apps.hammer.common.data.SceneBuffer import com.darkrockstudios.apps.hammer.common.data.SceneItem import com.darkrockstudios.apps.hammer.common.dependencyinjection.HammerComponent +import dev.icerock.moko.resources.StringResource interface SceneEditor : HammerComponent { val state: Value @@ -27,6 +28,7 @@ interface SceneEditor : HammerComponent { val sceneItem: SceneItem, val sceneBuffer: SceneBuffer? = null, val isEditingName: Boolean = false, - val isSavingDraft: Boolean = false + val isSavingDraft: Boolean = false, + val toast: StringResource? = null ) } \ No newline at end of file diff --git a/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/components/projecteditor/sceneeditor/SceneEditorComponent.kt b/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/components/projecteditor/sceneeditor/SceneEditorComponent.kt index 028782d9f..2e757fa27 100644 --- a/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/components/projecteditor/sceneeditor/SceneEditorComponent.kt +++ b/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/components/projecteditor/sceneeditor/SceneEditorComponent.kt @@ -8,6 +8,8 @@ import com.darkrockstudios.apps.hammer.common.components.ProjectComponentBase import com.darkrockstudios.apps.hammer.common.data.* import com.darkrockstudios.apps.hammer.common.data.drafts.SceneDraftRepository import com.darkrockstudios.apps.hammer.common.data.projecteditorrepository.ProjectEditorRepository +import com.darkrockstudios.apps.hammer.common.data.projectsrepository.ProjectsRepository +import com.darkrockstudios.apps.hammer.common.data.projectsrepository.ValidationFailedException import io.github.aakira.napier.Napier import kotlinx.coroutines.Job import kotlinx.coroutines.launch @@ -160,13 +162,25 @@ class SceneEditorComponent( } override suspend fun changeSceneName(newName: String) { - endSceneNameEdit() - projectEditor.renameScene(sceneDef, newName) + val result = ProjectsRepository.validateFileName(newName) - _state.getAndUpdate { - it.copy( - sceneItem = it.sceneItem.copy(name = newName) - ) + if (result.isSuccess) { + endSceneNameEdit() + projectEditor.renameScene(sceneDef, newName) + + _state.getAndUpdate { + it.copy( + sceneItem = it.sceneItem.copy(name = newName) + ) + } + } else { + (result.exceptionOrNull() as? ValidationFailedException)?.errorMessage?.let { message -> + _state.getAndUpdate { + it.copy( + toast = message + ) + } + } } } diff --git a/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/components/projectroot/ProjectRoot.kt b/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/components/projectroot/ProjectRoot.kt index ea3fcc854..881f309fb 100644 --- a/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/components/projectroot/ProjectRoot.kt +++ b/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/components/projectroot/ProjectRoot.kt @@ -3,6 +3,7 @@ package com.darkrockstudios.apps.hammer.common.components.projectroot import com.arkivanov.decompose.router.slot.ChildSlot import com.arkivanov.decompose.router.stack.ChildStack import com.arkivanov.decompose.value.Value +import com.darkrockstudios.apps.hammer.MR import com.darkrockstudios.apps.hammer.common.AppCloseManager import com.darkrockstudios.apps.hammer.common.components.encyclopedia.Encyclopedia import com.darkrockstudios.apps.hammer.common.components.notes.Notes @@ -11,6 +12,7 @@ import com.darkrockstudios.apps.hammer.common.components.projecthome.ProjectHome import com.darkrockstudios.apps.hammer.common.components.projectsync.ProjectSyncComponent import com.darkrockstudios.apps.hammer.common.components.timeline.TimeLine import com.darkrockstudios.apps.hammer.common.dependencyinjection.HammerComponent +import dev.icerock.moko.resources.StringResource interface ProjectRoot : AppCloseManager, HammerComponent { val routerState: Value>> @@ -63,11 +65,11 @@ interface ProjectRoot : AppCloseManager, HammerComponent { data class ProjectSync(val component: ProjectSyncComponent) : ModalDestination() } - enum class DestinationTypes(val text: String) { - Home("Home"), - Editor("Editor"), - Notes("Notes"), - Encyclopedia("Encyclopedia"), - TimeLine("Time Line"), + enum class DestinationTypes(val text: StringResource) { + Home(MR.strings.project_nav_home), + Editor(MR.strings.project_nav_scene_editor), + Notes(MR.strings.project_nav_notes), + Encyclopedia(MR.strings.project_nav_encyclopedia), + TimeLine(MR.strings.project_nav_time_line), } } \ No newline at end of file diff --git a/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/components/projectselection/ProjectSelection.kt b/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/components/projectselection/ProjectSelection.kt index 195276544..a34f66fc5 100644 --- a/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/components/projectselection/ProjectSelection.kt +++ b/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/components/projectselection/ProjectSelection.kt @@ -4,9 +4,11 @@ import com.arkivanov.decompose.router.slot.ChildSlot import com.arkivanov.decompose.value.Value import com.arkivanov.essenty.parcelable.Parcelable import com.arkivanov.essenty.parcelable.Parcelize +import com.darkrockstudios.apps.hammer.MR import com.darkrockstudios.apps.hammer.common.components.projectselection.accountsettings.AccountSettings import com.darkrockstudios.apps.hammer.common.components.projectselection.projectslist.ProjectsList import com.darkrockstudios.apps.hammer.common.dependencyinjection.HammerComponent +import dev.icerock.moko.resources.StringResource interface ProjectSelection : HammerComponent { val slot: Value> @@ -15,9 +17,9 @@ interface ProjectSelection : HammerComponent { fun showLocation(location: Locations) - enum class Locations(val text: String) { - Projects("Projects"), - Settings("Settings") + enum class Locations(val text: StringResource) { + Projects(MR.strings.project_select_nav_projects_list), + Settings(MR.strings.project_select_nav_account_settings) } sealed class Config(val location: Locations) : Parcelable { diff --git a/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/components/projectselection/projectslist/ProjectsList.kt b/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/components/projectselection/projectslist/ProjectsList.kt index cfc725f71..d6dd7f81a 100644 --- a/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/components/projectselection/projectslist/ProjectsList.kt +++ b/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/components/projectselection/projectslist/ProjectsList.kt @@ -7,6 +7,7 @@ import com.darkrockstudios.apps.hammer.common.data.ProjectDef import com.darkrockstudios.apps.hammer.common.data.projectsync.SyncLogMessage import com.darkrockstudios.apps.hammer.common.dependencyinjection.HammerComponent import com.darkrockstudios.apps.hammer.common.fileio.HPath +import dev.icerock.moko.resources.StringResource interface ProjectsList : HammerComponent { val state: Value @@ -26,7 +27,7 @@ interface ProjectsList : HammerComponent { val projectsPath: HPath, val isServerSynced: Boolean = false, val syncState: SyncState = SyncState(), - val toast: String? = null + val toast: StringResource? = null ) data class SyncState( diff --git a/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/components/projectselection/projectslist/ProjectsListComponent.kt b/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/components/projectselection/projectslist/ProjectsListComponent.kt index 079eb7e8b..e5350ff39 100644 --- a/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/components/projectselection/projectslist/ProjectsListComponent.kt +++ b/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/components/projectselection/projectslist/ProjectsListComponent.kt @@ -4,12 +4,14 @@ import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.value.MutableValue import com.arkivanov.decompose.value.Value import com.arkivanov.decompose.value.getAndUpdate +import com.darkrockstudios.apps.hammer.MR import com.darkrockstudios.apps.hammer.common.components.ComponentBase import com.darkrockstudios.apps.hammer.common.components.projecteditor.metadata.ProjectMetadata import com.darkrockstudios.apps.hammer.common.components.projectselection.ProjectData import com.darkrockstudios.apps.hammer.common.data.ProjectDef import com.darkrockstudios.apps.hammer.common.data.globalsettings.GlobalSettingsRepository import com.darkrockstudios.apps.hammer.common.data.projectsrepository.ProjectsRepository +import com.darkrockstudios.apps.hammer.common.data.projectsrepository.ValidationFailedException import com.darkrockstudios.apps.hammer.common.data.projectsync.* import com.darkrockstudios.apps.hammer.common.data.temporaryProjectTask import com.darkrockstudios.apps.hammer.common.dependencyinjection.injectMainDispatcher @@ -17,6 +19,7 @@ import com.darkrockstudios.apps.hammer.common.fileio.HPath import com.darkrockstudios.apps.hammer.common.fileio.okio.toHPath import com.darkrockstudios.apps.hammer.common.util.NetworkConnectivity import com.soywiz.kds.iterators.parallelMap +import dev.icerock.moko.resources.StringResource import io.github.aakira.napier.Napier import kotlinx.coroutines.* import kotlinx.coroutines.flow.first @@ -49,7 +52,7 @@ class ProjectsListComponent( ) override val state: Value = _state - private fun showToast(message: String) { + private fun showToast(message: StringResource) { _state.getAndUpdate { it.copy(toast = message) } @@ -153,13 +156,22 @@ class ProjectsListComponent( override fun selectProject(projectDef: ProjectDef) = onProjectSelected(projectDef) override fun createProject(projectName: String) { - if (projectsRepository.createProject(projectName)) { + val result = projectsRepository.createProject(projectName) + if (result.isSuccess) { if (projectsSynchronizer.isServerSynchronized()) { projectsSynchronizer.createProject(projectName) } Napier.i("Project created: $projectName") loadProjectList() } else { + (result.exceptionOrNull() as? ValidationFailedException)?.errorMessage?.let { message -> + _state.getAndUpdate { + it.copy( + toast = message + ) + } + } + Napier.e("Failed to create Project: $projectName") } } @@ -349,9 +361,9 @@ class ProjectsListComponent( scope.launch { syncProjects { success -> if (success) { - showToast("Projects synced") + showToast(MR.strings.projects_list_toast_sync_complete) } else { - showToast("Failed to sync projects") + showToast(MR.strings.projects_list_toast_sync_failed) } } } diff --git a/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/data/encyclopediarepository/EncyclopediaRepository.kt b/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/data/encyclopediarepository/EncyclopediaRepository.kt index 93609e3c2..94c16fc41 100644 --- a/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/data/encyclopediarepository/EncyclopediaRepository.kt +++ b/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/data/encyclopediarepository/EncyclopediaRepository.kt @@ -98,8 +98,8 @@ abstract class EncyclopediaRepository( abstract suspend fun reIdEntry(oldId: Int, newId: Int) companion object { - val ENTRY_NAME_PATTERN = Regex("""([\da-zA-Z ]+)""") - val ENTRY_FILENAME_PATTERN = Regex("""([a-zA-Z]+)-(\d+)-([\da-zA-Z ]+)\.toml""") + val ENTRY_NAME_PATTERN = Regex("""([\d\p{L}+ _']+)""") + val ENTRY_FILENAME_PATTERN = Regex("""([a-zA-Z]+)-(\d+)-([\d\p{L}+ _']+)\.toml""") const val ENCYCLOPEDIA_DIRECTORY = "encyclopedia" const val MAX_NAME_SIZE = 64 const val MAX_TAG_SIZE = 64 diff --git a/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/data/projecteditorrepository/ProjectEditorRepository.kt b/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/data/projecteditorrepository/ProjectEditorRepository.kt index 0bb3931c1..389131a87 100644 --- a/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/data/projecteditorrepository/ProjectEditorRepository.kt +++ b/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/data/projecteditorrepository/ProjectEditorRepository.kt @@ -423,7 +423,7 @@ abstract class ProjectEditorRepository( return sceneTree.findOrNull { it.id == id }?.parent?.value } - fun validateSceneName(sceneName: String) = projectsRepository.validateFileName(sceneName) + fun validateSceneName(sceneName: String): Result = ProjectsRepository.validateFileName(sceneName) protected abstract fun loadMetadata(): ProjectMetadata protected abstract fun saveMetadata(metadata: ProjectMetadata) @@ -452,7 +452,7 @@ abstract class ProjectEditorRepository( abstract fun reIdScene(oldId: Int, newId: Int) companion object { - val SCENE_FILENAME_PATTERN = Regex("""(\d+)-([\da-zA-Z _']+)-(\d+)(\.md)?(?:\.temp)?""") + val SCENE_FILENAME_PATTERN = Regex("""(\d+)-([\d\p{L}+ _']+)-(\d+)(\.md)?(?:\.temp)?""") val SCENE_BUFFER_FILENAME_PATTERN = Regex("""(\d+)\.md""") const val SCENE_FILENAME_EXTENSION = ".md" const val SCENE_DIRECTORY = "scenes" diff --git a/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/data/projecteditorrepository/ProjectEditorRepositoryOkio.kt b/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/data/projecteditorrepository/ProjectEditorRepositoryOkio.kt index 37d93e2e6..c8f0b2611 100644 --- a/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/data/projecteditorrepository/ProjectEditorRepositoryOkio.kt +++ b/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/data/projecteditorrepository/ProjectEditorRepositoryOkio.kt @@ -503,7 +503,7 @@ class ProjectEditorRepositoryOkio( ): SceneItem? { val cleanedNamed = name.trim() - return if (!validateSceneName(cleanedNamed)) { + return if (validateSceneName(cleanedNamed).isFailure) { Napier.d("Invalid scene name") null } else { diff --git a/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/data/projectsrepository/ProjectsRepository.kt b/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/data/projectsrepository/ProjectsRepository.kt index 86a22bd34..a2fbd9892 100644 --- a/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/data/projectsrepository/ProjectsRepository.kt +++ b/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/data/projectsrepository/ProjectsRepository.kt @@ -1,9 +1,11 @@ package com.darkrockstudios.apps.hammer.common.data.projectsrepository +import com.darkrockstudios.apps.hammer.MR import com.darkrockstudios.apps.hammer.common.components.projecteditor.metadata.ProjectMetadata import com.darkrockstudios.apps.hammer.common.data.ProjectDef import com.darkrockstudios.apps.hammer.common.dependencyinjection.DISPATCHER_DEFAULT import com.darkrockstudios.apps.hammer.common.fileio.HPath +import dev.icerock.moko.resources.StringResource import io.github.aakira.napier.Napier import kotlinx.coroutines.CoroutineScope import org.koin.core.component.KoinComponent @@ -19,34 +21,58 @@ abstract class ProjectsRepository : KoinComponent { abstract fun getProjectsDirectory(): HPath abstract fun getProjects(projectsDir: HPath = getProjectsDirectory()): List abstract fun getProjectDirectory(projectName: String): HPath - abstract fun createProject(projectName: String): Boolean + abstract fun createProject(projectName: String): Result abstract fun deleteProject(projectDef: ProjectDef): Boolean abstract suspend fun loadMetadata(projectDef: ProjectDef): ProjectMetadata? abstract fun ensureProjectDirectory() - fun validateFileName(fileName: String?): Boolean { - return if (fileName != null) { - val wasValid = fileNameValidations.map { - it.condition(fileName).also { isValid -> - if (!isValid) Napier.w { "$fileName Failed validation: ${it.name}" } - } - }.reduce { acc, b -> acc && b } + private data class Validator( + val name: String, + val errorMessage: StringResource, + val condition: (String) -> Boolean, + ) + + companion object { + const val MAX_FILENAME_LENGTH = 128 - Napier.i("$fileName was valid: $wasValid") + private val fileNameValidations = listOf( + Validator( + "Was Blank", + MR.strings.create_project_error_blank + ) { it.isNotBlank() }, + Validator( + "Invalid Characters", + MR.strings.create_project_error_invalid_characters + ) { Regex("""[\d\p{L}+ _']+""").matches(it) }, + Validator( + "Too Long", + MR.strings.create_project_error_too_long + ) { it.length <= MAX_FILENAME_LENGTH }, + ) + + fun validateFileName(fileName: String?): Result { + return if (fileName != null) { + var error: StringResource? = null + for (validator in fileNameValidations) { + if (validator.condition(fileName).not()) { + error = validator.errorMessage + break + } + } - wasValid - } else { - false + if (error == null) { + Napier.i("$fileName was valid") + Result.success(true) + } else { + Napier.i("$fileName was invalid: $error") + Result.failure(ValidationFailedException(error)) + } + } else { + Result.failure(ValidationFailedException(MR.strings.create_project_error_null_filename)) + } } } +} - private val fileNameValidations = listOf( - Validator("Was Blank") { it.isNotBlank() }, - Validator("Invalid Characters") { Regex("""[\da-zA-Z _']+""").matches(it) }, - ) - - private data class Validator( - val name: String, - val condition: (String) -> Boolean, - ) -} \ No newline at end of file +class ValidationFailedException(val errorMessage: StringResource) : IllegalArgumentException() +class ProjectCreationFailedException(val errorMessage: StringResource?) : IllegalArgumentException() \ No newline at end of file diff --git a/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/data/projectsrepository/ProjectsRepositoryOkio.kt b/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/data/projectsrepository/ProjectsRepositoryOkio.kt index 9ff14ee46..a30debbb0 100644 --- a/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/data/projectsrepository/ProjectsRepositoryOkio.kt +++ b/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/data/projectsrepository/ProjectsRepositoryOkio.kt @@ -1,6 +1,7 @@ package com.darkrockstudios.apps.hammer.common.data.projectsrepository import com.akuleshov7.ktoml.Toml +import com.darkrockstudios.apps.hammer.MR import com.darkrockstudios.apps.hammer.common.components.projecteditor.metadata.Info import com.darkrockstudios.apps.hammer.common.components.projecteditor.metadata.ProjectMetadata import com.darkrockstudios.apps.hammer.common.data.ProjectDef @@ -71,13 +72,14 @@ class ProjectsRepositoryOkio( return projectDir.toHPath() } - override fun createProject(projectName: String): Boolean { + override fun createProject(projectName: String): Result { val strippedName = projectName.trim() - return if (validateFileName(strippedName)) { + val result = validateFileName(strippedName) + return if (result.isSuccess) { val projectsDir = getProjectsDirectory().toOkioPath() val newProjectDir = projectsDir.div(strippedName) if (fileSystem.exists(newProjectDir)) { - false + Result.failure(ProjectCreationFailedException(MR.strings.create_project_error_already_exists)) } else { fileSystem.createDirectory(newProjectDir) @@ -97,10 +99,12 @@ class ProjectsRepositoryOkio( writeUtf8(metalToml) } - true + Result.success(true) } } else { - false + Result.failure( + ProjectCreationFailedException((result.exceptionOrNull() as? ValidationFailedException)?.errorMessage) + ) } } diff --git a/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/util/MokoUtils.kt b/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/util/MokoUtils.kt new file mode 100644 index 000000000..6a55ccc95 --- /dev/null +++ b/common/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/util/MokoUtils.kt @@ -0,0 +1,8 @@ +package com.darkrockstudios.apps.hammer.common.util + +import dev.icerock.moko.resources.StringResource + +expect class StrRes { + fun get(str: StringResource): String + fun get(str: StringResource, vararg args: Any): String +} \ No newline at end of file diff --git a/common/src/desktopMain/kotlin/com/darkrockstudios/apps/hammer/common/dependencyinjection/platformModule.kt b/common/src/desktopMain/kotlin/com/darkrockstudios/apps/hammer/common/dependencyinjection/platformModule.kt index c97520fd0..7af4239c4 100644 --- a/common/src/desktopMain/kotlin/com/darkrockstudios/apps/hammer/common/dependencyinjection/platformModule.kt +++ b/common/src/desktopMain/kotlin/com/darkrockstudios/apps/hammer/common/dependencyinjection/platformModule.kt @@ -1,9 +1,11 @@ package com.darkrockstudios.apps.hammer.common.dependencyinjection import com.darkrockstudios.apps.hammer.common.util.NetworkConnectivity +import com.darkrockstudios.apps.hammer.common.util.StrRes import org.koin.core.module.dsl.singleOf import org.koin.dsl.module actual val platformModule = module { singleOf(::NetworkConnectivity) + singleOf(::StrRes) } \ No newline at end of file diff --git a/common/src/desktopMain/kotlin/com/darkrockstudios/apps/hammer/common/util/MokoUtils.kt b/common/src/desktopMain/kotlin/com/darkrockstudios/apps/hammer/common/util/MokoUtils.kt new file mode 100644 index 000000000..6d10805b0 --- /dev/null +++ b/common/src/desktopMain/kotlin/com/darkrockstudios/apps/hammer/common/util/MokoUtils.kt @@ -0,0 +1,9 @@ +package com.darkrockstudios.apps.hammer.common.util + +import dev.icerock.moko.resources.StringResource +import dev.icerock.moko.resources.format + +actual class StrRes { + actual fun get(str: StringResource) = str.localized() + actual fun get(str: StringResource, vararg args: Any) = str.format(args).localized() +} \ No newline at end of file diff --git a/common/src/desktopTest/kotlin/repositories/ProjectsRepositoryTest.kt b/common/src/desktopTest/kotlin/repositories/ProjectsRepositoryTest.kt index b2105331f..d0c7df168 100644 --- a/common/src/desktopTest/kotlin/repositories/ProjectsRepositoryTest.kt +++ b/common/src/desktopTest/kotlin/repositories/ProjectsRepositoryTest.kt @@ -1,15 +1,19 @@ package repositories import com.akuleshov7.ktoml.Toml +import com.darkrockstudios.apps.hammer.MR import com.darkrockstudios.apps.hammer.common.data.ProjectDefinition import com.darkrockstudios.apps.hammer.common.data.globalsettings.GlobalSettings import com.darkrockstudios.apps.hammer.common.data.globalsettings.GlobalSettingsRepository +import com.darkrockstudios.apps.hammer.common.data.projectsrepository.ProjectsRepository import com.darkrockstudios.apps.hammer.common.data.projectsrepository.ProjectsRepositoryOkio +import com.darkrockstudios.apps.hammer.common.data.projectsrepository.ValidationFailedException import com.darkrockstudios.apps.hammer.common.dependencyinjection.createTomlSerializer import com.darkrockstudios.apps.hammer.common.fileio.okio.toHPath import com.darkrockstudios.apps.hammer.common.fileio.okio.toOkioPath import createProjectDirectories import createRootDirectory +import dev.icerock.moko.resources.StringResource import getProjectsDirectory import io.mockk.coEvery import io.mockk.coJustAwait @@ -23,10 +27,7 @@ import org.junit.After import org.junit.Before import projectNames import utils.BaseTest -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue +import kotlin.test.* @OptIn(ExperimentalCoroutinesApi::class) class ProjectsRepositoryTest : BaseTest() { @@ -57,7 +58,6 @@ class ProjectsRepositoryTest : BaseTest() { setupKoin() } - @After override fun tearDown() { super.tearDown() @@ -68,23 +68,33 @@ class ProjectsRepositoryTest : BaseTest() { fun `ProjectsRepository init`() = scope.runTest { val projDir = getProjectsDirectory() assertFalse(ffs.exists(projDir), "Dir should not have existed already") - val repo = ProjectsRepositoryOkio(ffs, toml, settingsRepo) - assertTrue(ffs.exists(projDir), "Init did not create project dir") } + private fun assertFailure(filename: String?, error: StringResource) { + val result = ProjectsRepository.validateFileName(filename) + assert(result.isFailure) + + val exception = result.exceptionOrNull() as? ValidationFailedException + assertNotNull(exception) + + assertEquals(error, exception.errorMessage) + } + @Test fun `Scene Name Validation`() = scope.runTest { - val repo = ProjectsRepositoryOkio(ffs, toml, settingsRepo) + listOf("good", "cliché", "one two", "one_two", "1234567890", "nums1234567890", "aZ").forEach { + assertTrue(ProjectsRepository.validateFileName(it).isSuccess) + } - assertTrue(repo.validateFileName("good")) - assertFalse(repo.validateFileName(null)) - assertFalse(repo.validateFileName("")) - assertFalse(repo.validateFileName("bad*bad")) - assertFalse(repo.validateFileName("bad-bad")) - assertFalse(repo.validateFileName("bad/bad")) - assertFalse(repo.validateFileName("""bad\bad""")) + assertFailure(null, MR.strings.create_project_error_null_filename) + assertFailure("", MR.strings.create_project_error_blank) + assertFailure(" ", MR.strings.create_project_error_blank) + + listOf("bad*bad", "bad-bad", "bad/bad", """bad\bad""").forEach { + assertFailure(it, MR.strings.create_project_error_invalid_characters) + } } @Test @@ -125,14 +135,14 @@ class ProjectsRepositoryTest : BaseTest() { val repo = ProjectsRepositoryOkio(ffs, toml, settingsRepo) val projectName = projectNames[0] - val created = repo.createProject(projectName) - assertTrue(created) + val result = repo.createProject(projectName) + assertTrue(result.isSuccess) val actualProjDir = getProjectsDirectory().div(projectName) assertTrue(ffs.exists(actualProjDir)) - val created2 = repo.createProject(projectName) - assertFalse(created2) + val result2 = repo.createProject(projectName) + assertFalse(result2.isSuccess) } @Test diff --git a/common/src/desktopTest/kotlin/repositories/projecteditor/ProjectEditorRepositoryOkioOtherTest.kt b/common/src/desktopTest/kotlin/repositories/projecteditor/ProjectEditorRepositoryOkioOtherTest.kt index 142590a14..63576b3b0 100644 --- a/common/src/desktopTest/kotlin/repositories/projecteditor/ProjectEditorRepositoryOkioOtherTest.kt +++ b/common/src/desktopTest/kotlin/repositories/projecteditor/ProjectEditorRepositoryOkioOtherTest.kt @@ -4,12 +4,14 @@ import OUT_OF_ORDER_PROJECT_NAME import PROJECT_1_NAME import PROJECT_2_NAME import com.akuleshov7.ktoml.Toml +import com.darkrockstudios.apps.hammer.MR import com.darkrockstudios.apps.hammer.common.data.ProjectDef import com.darkrockstudios.apps.hammer.common.data.SceneItem import com.darkrockstudios.apps.hammer.common.data.id.IdRepository import com.darkrockstudios.apps.hammer.common.data.projecteditorrepository.ProjectEditorRepository import com.darkrockstudios.apps.hammer.common.data.projecteditorrepository.ProjectEditorRepositoryOkio import com.darkrockstudios.apps.hammer.common.data.projectsrepository.ProjectsRepository +import com.darkrockstudios.apps.hammer.common.data.projectsrepository.ValidationFailedException import com.darkrockstudios.apps.hammer.common.data.projectsync.ClientProjectSynchronizer import com.darkrockstudios.apps.hammer.common.data.tree.Tree import com.darkrockstudios.apps.hammer.common.data.tree.TreeNode @@ -22,6 +24,7 @@ import createProject import io.mockk.coEvery import io.mockk.every import io.mockk.mockk +import io.mockk.mockkObject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import okio.Path.Companion.toPath @@ -47,6 +50,8 @@ class ProjectEditorRepositoryOkioOtherTest : BaseTest() { private var nextId = -1 private lateinit var toml: Toml + private val errorException = ValidationFailedException(MR.strings.create_project_error_null_filename) + private fun claimId(): Int { val id = nextId nextId++ @@ -100,6 +105,8 @@ class ProjectEditorRepositoryOkioOtherTest : BaseTest() { every { projectsRepo.getProjectsDirectory() } returns rootDir.toPath().div(ProjectEditorRepositoryOkioMoveTest.PROJ_DIR).toHPath() + mockkObject(ProjectsRepository.Companion) + setupKoin() } @@ -197,7 +204,7 @@ class ProjectEditorRepositoryOkioOtherTest : BaseTest() { fun `Create Scene, Invalid Scene Name`() = runTest { configure(PROJECT_1_NAME) - every { projectsRepo.validateFileName(any()) } returns false + every { ProjectsRepository.validateFileName(any()) } returns Result.failure(errorException) repo.initializeProjectEditor() @@ -210,7 +217,7 @@ class ProjectEditorRepositoryOkioOtherTest : BaseTest() { fun `Create Scene, Under Root`() = runTest { configure(PROJECT_1_NAME) - every { projectsRepo.validateFileName(any()) } returns true + every { ProjectsRepository.validateFileName(any()) } returns Result.success(true) repo.initializeProjectEditor() @@ -231,7 +238,7 @@ class ProjectEditorRepositoryOkioOtherTest : BaseTest() { fun `Create Scene, In Group`() = runTest { configure(PROJECT_1_NAME) - every { projectsRepo.validateFileName(any()) } returns true + every { ProjectsRepository.validateFileName(any()) } returns Result.success(true) repo.initializeProjectEditor() @@ -256,7 +263,7 @@ class ProjectEditorRepositoryOkioOtherTest : BaseTest() { fun `Create Group, In Root`() = runTest { configure(PROJECT_1_NAME) - every { projectsRepo.validateFileName(any()) } returns true + every { ProjectsRepository.validateFileName(any()) } returns Result.success(true) repo.initializeProjectEditor() verifyTreeAndFilesystem() @@ -275,7 +282,7 @@ class ProjectEditorRepositoryOkioOtherTest : BaseTest() { fun `Create Group, In Group`() = runTest { configure(PROJECT_1_NAME) - every { projectsRepo.validateFileName(any()) } returns true + every { ProjectsRepository.validateFileName(any()) } returns Result.success(true) repo.initializeProjectEditor() diff --git a/common/src/desktopTest/kotlin/repositories/projectsrepository/ProjectsRepositoryTest.kt b/common/src/desktopTest/kotlin/repositories/projectsrepository/ProjectsRepositoryTest.kt index d7d06de8a..f1881ad48 100644 --- a/common/src/desktopTest/kotlin/repositories/projectsrepository/ProjectsRepositoryTest.kt +++ b/common/src/desktopTest/kotlin/repositories/projectsrepository/ProjectsRepositoryTest.kt @@ -83,8 +83,8 @@ class ProjectsRepositoryTest : BaseTest() { val projectName = "Test Project" - val created = repo.createProject(projectName) - assertTrue { created } + val result = repo.createProject(projectName) + assertTrue { result.isSuccess } val projectPath = settings.projectsDirectory.toPath() / projectName ffs.exists(projectPath) diff --git a/common/src/iosMain/kotlin/com/darkrockstudios/apps/hammer/common/dependencyinjection/platformModule.kt b/common/src/iosMain/kotlin/com/darkrockstudios/apps/hammer/common/dependencyinjection/platformModule.kt index c97520fd0..7af4239c4 100644 --- a/common/src/iosMain/kotlin/com/darkrockstudios/apps/hammer/common/dependencyinjection/platformModule.kt +++ b/common/src/iosMain/kotlin/com/darkrockstudios/apps/hammer/common/dependencyinjection/platformModule.kt @@ -1,9 +1,11 @@ package com.darkrockstudios.apps.hammer.common.dependencyinjection import com.darkrockstudios.apps.hammer.common.util.NetworkConnectivity +import com.darkrockstudios.apps.hammer.common.util.StrRes import org.koin.core.module.dsl.singleOf import org.koin.dsl.module actual val platformModule = module { singleOf(::NetworkConnectivity) + singleOf(::StrRes) } \ No newline at end of file diff --git a/common/src/iosMain/kotlin/com/darkrockstudios/apps/hammer/common/util/MokoUtils.kt b/common/src/iosMain/kotlin/com/darkrockstudios/apps/hammer/common/util/MokoUtils.kt new file mode 100644 index 000000000..394e83e6c --- /dev/null +++ b/common/src/iosMain/kotlin/com/darkrockstudios/apps/hammer/common/util/MokoUtils.kt @@ -0,0 +1,8 @@ +package com.darkrockstudios.apps.hammer.common.util + +import dev.icerock.moko.resources.StringResource + +actual class StrRes { + actual fun get(str: StringResource) = "not implemented" + actual fun get(str: StringResource, vararg args: Any) = "not implemented" +} \ No newline at end of file diff --git a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/compose/KoinCompose.kt b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/compose/KoinCompose.kt index 1451b219f..efd5f2ad7 100644 --- a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/compose/KoinCompose.kt +++ b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/compose/KoinCompose.kt @@ -5,8 +5,10 @@ import androidx.compose.runtime.remember import com.darkrockstudios.apps.hammer.common.dependencyinjection.DISPATCHER_DEFAULT import com.darkrockstudios.apps.hammer.common.dependencyinjection.DISPATCHER_IO import com.darkrockstudios.apps.hammer.common.dependencyinjection.DISPATCHER_MAIN +import com.darkrockstudios.apps.hammer.common.util.StrRes import org.koin.core.qualifier.named import org.koin.java.KoinJavaComponent +import org.koin.mp.KoinPlatform.getKoin import kotlin.coroutines.CoroutineContext fun getDefaultDispatcher(): CoroutineContext { @@ -28,4 +30,7 @@ inline fun rememberDefaultDispatcher(): CoroutineContext = remember { getDefault inline fun rememberIoDispatcher(): CoroutineContext = remember { getIoDispatcher() } @Composable -inline fun rememberMainDispatcher(): CoroutineContext = remember { getMainDispatcher() } \ No newline at end of file +inline fun rememberMainDispatcher(): CoroutineContext = remember { getMainDispatcher() } + +@Composable +inline fun rememberStrRes() = remember { getKoin().get() } \ No newline at end of file diff --git a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/encyclopedia/BrowseEntriesUi.kt b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/encyclopedia/BrowseEntriesUi.kt index bb5091568..97b3c17c2 100644 --- a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/encyclopedia/BrowseEntriesUi.kt +++ b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/encyclopedia/BrowseEntriesUi.kt @@ -16,9 +16,11 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import com.arkivanov.decompose.extensions.compose.jetbrains.subscribeAsState +import com.darkrockstudios.apps.hammer.MR import com.darkrockstudios.apps.hammer.common.components.encyclopedia.BrowseEntries import com.darkrockstudios.apps.hammer.common.compose.ExposedDropDown import com.darkrockstudios.apps.hammer.common.compose.Ui +import com.darkrockstudios.apps.hammer.common.compose.moko.get import com.darkrockstudios.apps.hammer.common.compose.moveFocusOnTab import com.darkrockstudios.apps.hammer.common.data.encyclopediarepository.entry.EntryDef import com.darkrockstudios.apps.hammer.common.data.encyclopediarepository.entry.EntryType @@ -56,9 +58,9 @@ internal fun BoxWithConstraintsScope.BrowseEntriesUi( searchText = it component.updateFilter(searchText, selectedType) }, - label = { Text("Search by Name") }, + label = { Text(MR.strings.encyclopedia_search_hint.get()) }, singleLine = true, - placeholder = { Text("Search by Name") }, + placeholder = { Text(MR.strings.encyclopedia_search_hint.get()) }, modifier = Modifier.moveFocusOnTab().weight(1f), keyboardOptions = KeyboardOptions( autoCorrect = false, @@ -70,7 +72,7 @@ internal fun BoxWithConstraintsScope.BrowseEntriesUi( searchText = "" component.updateFilter(searchText, selectedType) }) { - Icon(imageVector = Icons.Filled.Clear, "Clear") + Icon(imageVector = Icons.Filled.Clear, MR.strings.encyclopedia_search_clear_button.get()) } }, ) @@ -81,7 +83,7 @@ internal fun BoxWithConstraintsScope.BrowseEntriesUi( modifier = Modifier.defaultMinSize(minWidth = 128.dp), padding = Ui.Padding.XL, items = types, - noneOption = "All", + noneOption = MR.strings.encyclopedia_category_all.get(), defaultIndex = state.filterType?.let { types.indexOf(state.filterType) + 1 } ?: 0 ) { item -> selectedType = item @@ -97,7 +99,7 @@ internal fun BoxWithConstraintsScope.BrowseEntriesUi( if (filteredEntries.isEmpty()) { item { Text( - "No Entries Found", + MR.strings.encyclopedia_browse_list_empty.get(), style = MaterialTheme.typography.headlineSmall, color = MaterialTheme.colorScheme.onBackground ) @@ -124,7 +126,7 @@ internal fun BoxWithConstraintsScope.BrowseEntriesUi( ) { Icon( Icons.Rounded.Add, - "Create Entry" + MR.strings.encyclopedia_create_button.get() ) } } diff --git a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/encyclopedia/CreateEntryUi.kt b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/encyclopedia/CreateEntryUi.kt index 8b65cb7f2..f673e42dd 100644 --- a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/encyclopedia/CreateEntryUi.kt +++ b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/encyclopedia/CreateEntryUi.kt @@ -18,11 +18,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp +import com.darkrockstudios.apps.hammer.MR import com.darkrockstudios.apps.hammer.common.components.encyclopedia.CreateEntry -import com.darkrockstudios.apps.hammer.common.compose.ExposedDropDown -import com.darkrockstudios.apps.hammer.common.compose.ImageItem -import com.darkrockstudios.apps.hammer.common.compose.SimpleConfirm -import com.darkrockstudios.apps.hammer.common.compose.Ui +import com.darkrockstudios.apps.hammer.common.compose.* +import com.darkrockstudios.apps.hammer.common.compose.moko.get import com.darkrockstudios.apps.hammer.common.data.encyclopediarepository.EncyclopediaRepository import com.darkrockstudios.apps.hammer.common.data.encyclopediarepository.EntryError import com.darkrockstudios.apps.hammer.common.data.encyclopediarepository.entry.EntryType @@ -41,6 +40,7 @@ internal fun CreateEntryUi( modifier: Modifier, close: () -> Unit ) { + val strRes = rememberStrRes() var newEntryNameText by rememberSaveable { mutableStateOf("") } var newEntryContentText by rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) @@ -64,13 +64,13 @@ internal fun CreateEntryUi( .widthIn(128.dp, 420.dp) ) { Text( - "Create New Entry", + MR.strings.encyclopedia_create_entry_header.get(), modifier = Modifier.padding(PaddingValues(bottom = Ui.Padding.XL)), style = MaterialTheme.typography.displayMedium ) Text( - "Type:", + MR.strings.encyclopedia_create_entry_type_label.get(), style = MaterialTheme.typography.titleMedium, modifier = Modifier.padding(bottom = Ui.Padding.M) ) @@ -93,21 +93,21 @@ internal fun CreateEntryUi( .padding(PaddingValues(top = Ui.Padding.XL, bottom = Ui.Padding.L)), value = newEntryNameText, onValueChange = { newEntryNameText = it }, - placeholder = { Text("Name") } + placeholder = { Text(MR.strings.encyclopedia_create_entry_name_label.get()) } ) TextField( modifier = Modifier.fillMaxWidth().padding(PaddingValues(bottom = Ui.Padding.L)), value = newTagsText, onValueChange = { newTagsText = it }, - placeholder = { Text("Tags (space seperated)") } + placeholder = { Text(MR.strings.encyclopedia_create_entry_tags_label.get()) } ) OutlinedTextField( value = newEntryContentText, onValueChange = { newEntryContentText = it }, modifier = Modifier.fillMaxWidth().padding(PaddingValues(bottom = Ui.Padding.L)), - placeholder = { Text(text = "Describe your entry") }, + placeholder = { Text(text = MR.strings.encyclopedia_create_entry_body_hint.get()) }, maxLines = 10, ) @@ -130,12 +130,15 @@ internal fun CreateEntryUi( .align(Alignment.TopEnd), onClick = { imagePath = null } ) { - Icon(Icons.Default.Delete, "Remove Image") + Icon( + Icons.Default.Delete, + MR.strings.encyclopedia_create_entry_remove_image_button.get() + ) } } } else { Button(onClick = { showFilePicker = true }) { - Text("Select Image") + Text(MR.strings.encyclopedia_create_entry_select_image_button.get()) } } } @@ -154,32 +157,49 @@ internal fun CreateEntryUi( tags = newTagsText.splitToSequence(" ").toList(), imagePath = imagePath ) + when (result.error) { - EntryError.NAME_TOO_LONG -> scope.launch { snackbarHostState.showSnackbar("Entry Name was too long. Max ${EncyclopediaRepository.MAX_NAME_SIZE}") } + EntryError.NAME_TOO_LONG -> scope.launch { + snackbarHostState.showSnackbar( + strRes.get( + MR.strings.encyclopedia_create_entry_toast_too_long, + EncyclopediaRepository.MAX_NAME_SIZE + ) + ) + } + EntryError.NAME_INVALID_CHARACTERS -> scope.launch { snackbarHostState.showSnackbar( - "Entry Name must be alpha-numeric" + strRes.get(MR.strings.encyclopedia_create_entry_toast_invalid_name) + ) + } + + EntryError.TAG_TOO_LONG -> scope.launch { + snackbarHostState.showSnackbar( + strRes.get( + MR.strings.encyclopedia_create_entry_toast_tag_too_long, + EncyclopediaRepository.MAX_TAG_SIZE + ) ) } - EntryError.TAG_TOO_LONG -> scope.launch { snackbarHostState.showSnackbar("Tag is too long. Max ${EncyclopediaRepository.MAX_TAG_SIZE}") } EntryError.NONE -> { newEntryNameText = "" close() - scope.launch { snackbarHostState.showSnackbar("Entry Created") } + scope.launch { snackbarHostState.showSnackbar(strRes.get(MR.strings.encyclopedia_create_entry_toast_success)) } } } } } ) { - Text("Create") + Text(MR.strings.encyclopedia_create_entry_create_button.get()) } Button( modifier = Modifier.weight(1f).padding(PaddingValues(start = Ui.Padding.XL)), onClick = { discardConfirm = true } ) { - Text("Cancel") + Text(MR.strings.encyclopedia_create_entry_cancel_button.get()) } } } @@ -197,7 +217,7 @@ internal fun CreateEntryUi( if (discardConfirm) { SimpleConfirm( - title = "Discard New Entry?", + title = MR.strings.encyclopedia_create_entry_discard_title.get(), onDismiss = { discardConfirm = false } ) { discardConfirm = false diff --git a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/encyclopedia/EncyclopediaEntryItem.kt b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/encyclopedia/EncyclopediaEntryItem.kt index 86e3b2b91..c5db102ee 100644 --- a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/encyclopedia/EncyclopediaEntryItem.kt +++ b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/encyclopedia/EncyclopediaEntryItem.kt @@ -14,8 +14,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp +import com.darkrockstudios.apps.hammer.MR import com.darkrockstudios.apps.hammer.common.components.encyclopedia.BrowseEntries import com.darkrockstudios.apps.hammer.common.compose.* +import com.darkrockstudios.apps.hammer.common.compose.moko.get import com.darkrockstudios.apps.hammer.common.data.encyclopediarepository.entry.EntryContent import com.darkrockstudios.apps.hammer.common.data.encyclopediarepository.entry.EntryDef import com.darkrockstudios.apps.hammer.common.data.encyclopediarepository.entry.EntryType @@ -118,7 +120,7 @@ internal fun EncyclopediaEntryItem( tags = content.tags, ) } else { - Text("Error: Failed to load entry!") + Text(MR.strings.encyclopedia_entry_load_error.get()) } } } diff --git a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/encyclopedia/ViewEntryUi.kt b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/encyclopedia/ViewEntryUi.kt index 6840117a9..90b21e666 100644 --- a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/encyclopedia/ViewEntryUi.kt +++ b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/encyclopedia/ViewEntryUi.kt @@ -17,8 +17,10 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.arkivanov.decompose.extensions.compose.jetbrains.subscribeAsState +import com.darkrockstudios.apps.hammer.MR import com.darkrockstudios.apps.hammer.common.components.encyclopedia.ViewEntry import com.darkrockstudios.apps.hammer.common.compose.* +import com.darkrockstudios.apps.hammer.common.compose.moko.get import com.darkrockstudios.libraries.mpfilepicker.FilePicker import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -33,6 +35,7 @@ internal fun ViewEntryUi( snackbarHostState: SnackbarHostState, closeEntry: () -> Unit, ) { + val strRes = rememberStrRes() val dispatcherMain = rememberMainDispatcher() val dispatcherDefault = rememberDefaultDispatcher() val state by component.state.subscribeAsState() @@ -81,13 +84,13 @@ internal fun ViewEntryUi( } scope.launch { - snackbarHostState.showSnackbar("Entry Saved") + snackbarHostState.showSnackbar(strRes.get(MR.strings.encyclopedia_entry_edit_save_toast)) } } }) { Icon( Icons.Filled.Check, - "Save", + MR.strings.encyclopedia_entry_edit_save_button.get(), tint = MaterialTheme.colorScheme.onSurface ) } @@ -95,7 +98,7 @@ internal fun ViewEntryUi( IconButton(onClick = { discardConfirm = true }) { Icon( Icons.Filled.Cancel, - "Cancel", + MR.strings.encyclopedia_entry_edit_cancel_button.get(), tint = MaterialTheme.colorScheme.error ) } @@ -114,8 +117,8 @@ internal fun ViewEntryUi( if (discardConfirm) { SimpleConfirm( - title = "Discard Changes?", - message = "You will lose any changes you have made.", + title = MR.strings.encyclopedia_entry_discard_title.get(), + message = MR.strings.encyclopedia_entry_discard_message.get(), onDismiss = { discardConfirm = false } ) { entryNameText = content.name @@ -141,7 +144,7 @@ internal fun ViewEntryUi( ) { Icon( Icons.Filled.Close, - contentDescription = "Close Entry", + contentDescription = MR.strings.encyclopedia_entry_close_button.get(), tint = MaterialTheme.colorScheme.onSurface ) } @@ -153,7 +156,7 @@ internal fun ViewEntryUi( modifier = Modifier.wrapContentHeight().fillMaxWidth(), value = entryNameText, onValueChange = { entryNameText = it }, - placeholder = { Text("Name") } + placeholder = { Text(MR.strings.encyclopedia_entry_name_hint.get()) } ) } else { Text( @@ -212,8 +215,8 @@ internal fun ViewEntryUi( if (state.showDeleteImageDialog) { SimpleConfirm( - title = "Delete Image?", - message = "This cannot be undone!", + title = MR.strings.encyclopedia_entry_delete_image_title.get(), + message = MR.strings.encyclopedia_entry_delete_image_message.get(), onDismiss = { component.closeDeleteImageDialog() } ) { scope.launch { component.removeEntryImage() } @@ -223,8 +226,8 @@ internal fun ViewEntryUi( if (state.showDeleteEntryDialog) { SimpleConfirm( - title = "Delete Entry?", - message = "This cannot be undone!", + title = MR.strings.encyclopedia_entry_delete_title.get(), + message = MR.strings.encyclopedia_entry_delete_message.get(), onDismiss = { component.closeDeleteEntryDialog() } ) { scope.launch(dispatcherDefault) { @@ -232,7 +235,7 @@ internal fun ViewEntryUi( withContext(dispatcherMain) { closeEntry() } - snackbarHostState.showSnackbar("Entry Deleted") + snackbarHostState.showSnackbar(strRes.get(MR.strings.encyclopedia_entry_delete_toast)) } } component.closeDeleteEntryDialog() @@ -241,8 +244,8 @@ internal fun ViewEntryUi( if (closeConfirm) { SimpleConfirm( - title = "Discard Changes?", - message = "You will lose any changes you have made.", + title = MR.strings.encyclopedia_entry_discard_title.get(), + message = MR.strings.encyclopedia_entry_discard_message.get(), onDismiss = { closeConfirm = false } ) { closeConfirm = false @@ -300,17 +303,26 @@ private fun Contents( if (content != null) { Column { + LaunchedEffect(entryText) { + if (entryText.isBlank()) { + beginEdit() + } + } + if (editText) { OutlinedTextField( value = entryText, onValueChange = setEntryText, modifier = Modifier.fillMaxWidth().padding(PaddingValues(bottom = Ui.Padding.XL)), - placeholder = { Text(text = "Describe your entry") }, + placeholder = { Text(text = MR.strings.encyclopedia_entry_body_empty_placeholder.get()) }, maxLines = 10, ) } else { + val text = entryText.ifBlank { + MR.strings.encyclopedia_entry_body_empty_label.get() + } Text( - entryText, + text, modifier = Modifier.fillMaxWidth().clickable { beginEdit() }, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onBackground, diff --git a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/notes/ConfirmDeleteDialog.kt b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/notes/ConfirmDeleteDialog.kt index 015c7ee0d..77e72abbd 100644 --- a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/notes/ConfirmDeleteDialog.kt +++ b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/notes/ConfirmDeleteDialog.kt @@ -2,8 +2,11 @@ package com.darkrockstudios.apps.hammer.common.notes import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable +import com.darkrockstudios.apps.hammer.MR import com.darkrockstudios.apps.hammer.common.components.notes.Notes import com.darkrockstudios.apps.hammer.common.compose.SimpleConfirm +import com.darkrockstudios.apps.hammer.common.compose.moko.get +import com.darkrockstudios.apps.hammer.common.compose.rememberStrRes import com.darkrockstudios.apps.hammer.common.data.notesrepository.note.NoteContent import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -15,15 +18,19 @@ internal fun ConfirmDeleteDialog( snackbarHostState: SnackbarHostState, scope: CoroutineScope, ) { + val strRes = rememberStrRes() + SimpleConfirm( - title = "Delete Note?", - message = "This action can not be un-done.", + title = MR.strings.notes_delete_title.get(), + message = MR.strings.notes_delete_message.get(), onDismiss = { component.dismissConfirmDelete() } ) { scope.launch { component.deleteNote(note.id) component.dismissConfirmDelete() - scope.launch { snackbarHostState.showSnackbar("Note ${note.id} Deleted") } + scope.launch { + snackbarHostState.showSnackbar(strRes.get(MR.strings.notes_delete_toast_success, note.id)) + } } } } \ No newline at end of file diff --git a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/notes/CreateNoteDialog.kt b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/notes/CreateNoteDialog.kt index 24fe18cd1..a5e0a00f3 100644 --- a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/notes/CreateNoteDialog.kt +++ b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/notes/CreateNoteDialog.kt @@ -5,10 +5,13 @@ import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import com.darkrockstudios.apps.hammer.MR import com.darkrockstudios.apps.hammer.common.components.notes.Notes import com.darkrockstudios.apps.hammer.common.compose.MpDialog import com.darkrockstudios.apps.hammer.common.compose.Ui +import com.darkrockstudios.apps.hammer.common.compose.moko.get import com.darkrockstudios.apps.hammer.common.compose.rememberMainDispatcher +import com.darkrockstudios.apps.hammer.common.compose.rememberStrRes import com.darkrockstudios.apps.hammer.common.data.notesrepository.NoteError import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -21,9 +24,11 @@ internal fun CreateNoteDialog( snackbarHostState: SnackbarHostState, scope: CoroutineScope, ) { + val strRes = rememberStrRes() + MpDialog( visible = true, - title = "Create Note", + title = MR.strings.notes_create_header.get(), onCloseRequest = { component.dismissCreate() } ) { val mainDispatcher = rememberMainDispatcher() @@ -37,7 +42,7 @@ internal fun CreateNoteDialog( .align(Alignment.Center) ) { Text( - "New Note:", + MR.strings.notes_create_body_hint.get(), style = MaterialTheme.typography.headlineLarge ) @@ -60,22 +65,30 @@ internal fun CreateNoteDialog( val result = component.createNote(newNoteText) newNoteError = !result.isSuccess when (result) { - NoteError.TOO_LONG -> scope.launch { snackbarHostState.showSnackbar("Note was too long") } - NoteError.EMPTY -> scope.launch { snackbarHostState.showSnackbar("Note was empty") } + NoteError.TOO_LONG -> scope.launch { + snackbarHostState.showSnackbar(strRes.get(MR.strings.notes_create_toast_too_long)) + } + + NoteError.EMPTY -> scope.launch { + snackbarHostState.showSnackbar(strRes.get(MR.strings.notes_create_toast_empty)) + } + NoteError.NONE -> { withContext(mainDispatcher) { newNoteText = "" } - scope.launch { snackbarHostState.showSnackbar("Note Created") } + scope.launch { + snackbarHostState.showSnackbar(strRes.get(MR.strings.notes_create_toast_success)) + } } } } }) { - Text("Create") + Text(MR.strings.notes_create_create_button.get()) } Button(onClick = { component.dismissCreate() }) { - Text("Cancel") + Text(MR.strings.notes_create_cancel_button.get()) } } } diff --git a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/notes/NotesUi.kt b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/notes/NotesUi.kt index dcbc36636..de6e8252f 100644 --- a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/notes/NotesUi.kt +++ b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/notes/NotesUi.kt @@ -17,9 +17,11 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.arkivanov.decompose.extensions.compose.jetbrains.subscribeAsState +import com.darkrockstudios.apps.hammer.MR import com.darkrockstudios.apps.hammer.common.components.notes.Notes import com.darkrockstudios.apps.hammer.common.compose.SimpleConfirm import com.darkrockstudios.apps.hammer.common.compose.Ui +import com.darkrockstudios.apps.hammer.common.compose.moko.get import com.darkrockstudios.apps.hammer.common.compose.rememberMainDispatcher import com.darkrockstudios.apps.hammer.common.data.notesrepository.note.NoteContent import com.darkrockstudios.apps.hammer.common.data.text.markdownToAnnotatedString @@ -41,7 +43,7 @@ fun NotesUi( Box(modifier = Modifier.fillMaxSize().padding(Ui.Padding.XL)) { Column { Text( - "Notes", + MR.strings.notes_header.get(), style = MaterialTheme.typography.headlineLarge, color = MaterialTheme.colorScheme.onBackground ) @@ -56,7 +58,7 @@ fun NotesUi( if (state.notes.isEmpty()) { item { Text( - "No Notes Found", + MR.strings.notes_list_empty.get(), style = MaterialTheme.typography.headlineSmall, color = MaterialTheme.colorScheme.onBackground ) @@ -78,7 +80,7 @@ fun NotesUi( onClick = { component.showCreate() }, modifier = Modifier.align(Alignment.BottomEnd) ) { - Icon(Icons.Filled.Create, "Create Note") + Icon(Icons.Filled.Create, MR.strings.notes_create_note_button.get()) } SnackbarHost(snackbarHostState, modifier = Modifier.align(Alignment.BottomCenter)) @@ -127,7 +129,7 @@ fun NoteItem( }) { Icon( Icons.Filled.Check, - "Rename", + MR.strings.notes_note_item_action_rename.get(), tint = MaterialTheme.colorScheme.onSurface ) } @@ -136,7 +138,7 @@ fun NoteItem( }) { Icon( Icons.Filled.Cancel, - "Cancel", + MR.strings.notes_note_item_action_cancel.get(), tint = MaterialTheme.colorScheme.error ) } @@ -161,7 +163,7 @@ fun NoteItem( IconButton( onClick = { component.confirmDelete(note) }, ) { - Icon(Icons.Filled.Delete, "Delete") + Icon(Icons.Filled.Delete, MR.strings.notes_note_item_action_delete.get()) } } Spacer(modifier = Modifier.size(Ui.Padding.L)) @@ -180,8 +182,8 @@ fun NoteItem( if (discardConfirm) { SimpleConfirm( - title = "Discard Changes?", - message = "You will lose any changes you have made.", + title = MR.strings.notes_discard_dialog_title.get(), + message = MR.strings.notes_discard_dialog_message.get(), onDismiss = { discardConfirm = false } ) { isEditing = false diff --git a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/drafts/DraftCompareUi.kt b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/drafts/DraftCompareUi.kt index 04987f811..48cc11d77 100644 --- a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/drafts/DraftCompareUi.kt +++ b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/drafts/DraftCompareUi.kt @@ -13,9 +13,12 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontStyle import com.arkivanov.decompose.extensions.compose.jetbrains.subscribeAsState +import com.darkrockstudios.apps.hammer.MR import com.darkrockstudios.apps.hammer.common.components.projecteditor.drafts.DraftCompare import com.darkrockstudios.apps.hammer.common.compose.LocalScreenCharacteristic import com.darkrockstudios.apps.hammer.common.compose.Ui +import com.darkrockstudios.apps.hammer.common.compose.moko.get +import com.darkrockstudios.apps.hammer.common.compose.rememberStrRes import com.darkrockstudios.apps.hammer.common.projecteditor.sceneeditor.getInitialEditorContent import com.darkrockstudios.richtexteditor.ui.RichTextEditor import com.darkrockstudios.richtexteditor.ui.defaultRichTextFieldStyle @@ -34,7 +37,7 @@ fun DraftCompareUi(component: DraftCompare) { ) { Icon( Icons.Default.Cancel, - contentDescription = "Cancel", + contentDescription = MR.strings.draft_compare_cancel_button.get(), tint = MaterialTheme.colorScheme.onBackground ) } @@ -78,17 +81,17 @@ private fun CurrentContent( Card(modifier = modifier.padding(Ui.Padding.L)) { Column(modifier = Modifier.padding(Ui.Padding.L)) { Text( - "Merge with Current", + MR.strings.draft_compare_current_header.get(), style = MaterialTheme.typography.headlineLarge ) Text( - "Copy parts of the draft into here", + MR.strings.draft_compare_current_subheader.get(), style = MaterialTheme.typography.bodySmall, fontStyle = FontStyle.Italic ) Button(onClick = { component.pickMerged() }) { - Text("Take Merged") + Text(MR.strings.draft_compare_current_accept_button.get()) } RichTextEditor( @@ -98,7 +101,7 @@ private fun CurrentContent( sceneText = rtv }, textFieldStyle = defaultRichTextFieldStyle().copy( - placeholder = "Begin writing your Scene here", + placeholder = MR.strings.draft_compare_current_body_placeholder.get(), textColor = MaterialTheme.colorScheme.onBackground, placeholderColor = MaterialTheme.colorScheme.onBackground, ) @@ -112,6 +115,7 @@ private fun DraftContent( modifier: Modifier, component: DraftCompare, ) { + val strRes = rememberStrRes() val state by component.state.subscribeAsState() var draftText by remember(state.draftContent) { mutableStateOf(getInitialEditorContent(state.draftContent)) } @@ -126,16 +130,16 @@ private fun DraftContent( */ Text( - "Draft: ${component.draftDef.draftName}", + strRes.get(MR.strings.draft_compare_draft_header, component.draftDef.draftName), style = MaterialTheme.typography.headlineLarge ) Text( - "No edits here will be saved", + MR.strings.draft_compare_draft_subheader.get(), style = MaterialTheme.typography.bodySmall, fontStyle = FontStyle.Italic ) Button(onClick = { component.pickDraft() }) { - Text("Take this whole draft") + Text(MR.strings.draft_compare_draft_accept_button.get()) } RichTextEditor( @@ -145,7 +149,7 @@ private fun DraftContent( draftText = rtv }, textFieldStyle = defaultRichTextFieldStyle().copy( - placeholder = "Draft", + placeholder = MR.strings.draft_compare_draft_body_placeholder.get(), textColor = MaterialTheme.colorScheme.onBackground, placeholderColor = MaterialTheme.colorScheme.onBackground, ), diff --git a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/drafts/DraftsListUi.kt b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/drafts/DraftsListUi.kt index 33ea9a0db..b7b9ffefd 100644 --- a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/drafts/DraftsListUi.kt +++ b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/drafts/DraftsListUi.kt @@ -15,9 +15,12 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.arkivanov.decompose.extensions.compose.jetbrains.subscribeAsState +import com.darkrockstudios.apps.hammer.MR import com.darkrockstudios.apps.hammer.common.components.projecteditor.drafts.DraftsList import com.darkrockstudios.apps.hammer.common.compose.LocalScreenCharacteristic import com.darkrockstudios.apps.hammer.common.compose.Ui +import com.darkrockstudios.apps.hammer.common.compose.moko.get +import com.darkrockstudios.apps.hammer.common.compose.rememberStrRes import com.darkrockstudios.apps.hammer.common.data.drafts.DraftDef import com.darkrockstudios.apps.hammer.common.util.formatLocal @@ -25,6 +28,7 @@ import com.darkrockstudios.apps.hammer.common.util.formatLocal fun DraftsListUi( component: DraftsList, ) { + val strRes = rememberStrRes() val state by component.state.subscribeAsState() LaunchedEffect(state.sceneItem) { @@ -43,12 +47,12 @@ fun DraftsListUi( onClick = { component.cancel() }, modifier = Modifier.align(Alignment.End) ) { - Icon(Icons.Default.Close, "Close Drafts") + Icon(Icons.Default.Close, MR.strings.draft_list_close_button.get()) } } Text( - "${state.sceneItem.name} Drafts:", + strRes.get(MR.strings.draft_list_header, state.sceneItem.name), style = MaterialTheme.typography.headlineLarge, color = MaterialTheme.colorScheme.onSurface ) @@ -61,7 +65,8 @@ fun DraftsListUi( state.apply { if (drafts.isEmpty()) { item { - Text("No Drafts Found") + + Text(MR.strings.draft_list_empty.get()) } } else { items(drafts.size) { index -> @@ -99,7 +104,7 @@ fun DraftItem( draftDef.draftTimestamp.formatLocal("dd MMM `yy") } Text( - "Created: $date", + MR.strings.draft_list_item_created.get(date), style = MaterialTheme.typography.bodySmall ) } diff --git a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/sceneeditor/SaveDraftDialog.kt b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/sceneeditor/SaveDraftDialog.kt index 1d9f36dd7..2e91fb5fa 100644 --- a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/sceneeditor/SaveDraftDialog.kt +++ b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/sceneeditor/SaveDraftDialog.kt @@ -8,10 +8,13 @@ import androidx.compose.material3.TextField import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import com.darkrockstudios.apps.hammer.MR import com.darkrockstudios.apps.hammer.common.components.projecteditor.sceneeditor.SceneEditor import com.darkrockstudios.apps.hammer.common.compose.MpDialog import com.darkrockstudios.apps.hammer.common.compose.Ui +import com.darkrockstudios.apps.hammer.common.compose.moko.get import com.darkrockstudios.apps.hammer.common.compose.rememberMainDispatcher +import com.darkrockstudios.apps.hammer.common.compose.rememberStrRes import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -22,13 +25,14 @@ internal fun SaveDraftDialog( component: SceneEditor, showSnackbar: (message: String) -> Unit ) { + val strRes = rememberStrRes() val scope = rememberCoroutineScope() val mainDispatcher = rememberMainDispatcher() var draftName by remember { mutableStateOf("") } MpDialog( visible = state.isSavingDraft, - title = "Save Draft:", + title = MR.strings.save_draft_dialog_title.get(), onCloseRequest = { component.endSaveDraft() draftName = "" @@ -44,7 +48,7 @@ internal fun SaveDraftDialog( value = draftName, onValueChange = { draftName = it }, singleLine = true, - placeholder = { Text("Draft name") } + placeholder = { Text(MR.strings.save_draft_dialog_name_hint.get()) } ) Spacer(modifier = Modifier.size(Ui.Padding.XL)) @@ -60,17 +64,17 @@ internal fun SaveDraftDialog( withContext(mainDispatcher) { draftName = "" } - showSnackbar("Draft Saved") + showSnackbar(strRes.get(MR.strings.save_draft_dialog_toast_success)) } } }) { - Text("Save") + Text(MR.strings.save_draft_dialog_save_button.get()) } Button(onClick = { component.endSaveDraft() draftName = "" }) { - Text("Cancel") + Text(MR.strings.save_draft_dialog_cancel_button.get()) } } } diff --git a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/sceneeditor/SceneEditorUi.kt b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/sceneeditor/SceneEditorUi.kt index 954aa3c87..47c7041c7 100644 --- a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/sceneeditor/SceneEditorUi.kt +++ b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/sceneeditor/SceneEditorUi.kt @@ -14,9 +14,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.unit.dp import com.arkivanov.decompose.extensions.compose.jetbrains.subscribeAsState +import com.darkrockstudios.apps.hammer.MR import com.darkrockstudios.apps.hammer.common.components.projecteditor.sceneeditor.SceneEditor import com.darkrockstudios.apps.hammer.common.compose.ComposeRichText import com.darkrockstudios.apps.hammer.common.compose.Ui +import com.darkrockstudios.apps.hammer.common.compose.moko.get +import com.darkrockstudios.apps.hammer.common.compose.rememberStrRes import com.darkrockstudios.richtexteditor.model.Style import com.darkrockstudios.richtexteditor.ui.RichTextEditor import com.darkrockstudios.richtexteditor.ui.defaultRichTextFieldStyle @@ -29,6 +32,7 @@ fun SceneEditorUi( modifier: Modifier = Modifier, drawableKlass: Any? = null ) { + val strRes = rememberStrRes() val scope = rememberCoroutineScope() val state by component.state.subscribeAsState() val lastForceUpdate by component.lastForceUpdate.subscribeAsState() @@ -44,6 +48,12 @@ fun SceneEditorUi( sceneText = getInitialEditorContent(state.sceneBuffer?.content) } + LaunchedEffect(state.toast) { + state.toast?.let { message -> + snackbarHostState.showSnackbar(strRes.get(message)) + } + } + Box(modifier = modifier) { Column(modifier = Modifier.fillMaxSize()) { Row(verticalAlignment = Alignment.CenterVertically) { @@ -54,19 +64,19 @@ fun SceneEditorUi( value = editSceneNameValue, onValueChange = { editSceneNameValue = it }, modifier = Modifier.padding(Ui.Padding.XL), - label = { Text("Scene Name") } + label = { Text(MR.strings.scene_editor_name_hint.get()) } ) IconButton(onClick = { scope.launch { component.changeSceneName(editSceneNameValue) } }) { Icon( Icons.Filled.Check, - "Rename", + MR.strings.scene_editor_rename_button.get(), tint = MaterialTheme.colorScheme.onSurface ) } IconButton(onClick = component::endSceneNameEdit) { Icon( Icons.Filled.Cancel, - "Cancel", + MR.strings.scene_editor_cancel_button.get(), tint = MaterialTheme.colorScheme.error ) } @@ -86,19 +96,21 @@ fun SceneEditorUi( val unsaved = state.sceneBuffer?.dirty == true if (unsaved) { - Badge(modifier = Modifier.align(Alignment.Top).padding(top = Ui.Padding.L)) { Text("Unsaved") } + Badge( + modifier = Modifier.align(Alignment.Top).padding(top = Ui.Padding.L) + ) { Text(MR.strings.scene_editor_unsaved_chip.get()) } Spacer(modifier = Modifier.weight(1f)) IconButton(onClick = { scope.launch { component.storeSceneContent() - scope.launch { snackbarHostState.showSnackbar("Saved") } + scope.launch { snackbarHostState.showSnackbar(strRes.get(MR.strings.scene_editor_toast_save_successful)) } } }) { Icon( Icons.Filled.Save, - contentDescription = "Save", + contentDescription = MR.strings.scene_editor_save_button.get(), tint = MaterialTheme.colorScheme.onSurface ) } @@ -149,7 +161,7 @@ fun SceneEditorUi( component.onContentChanged(ComposeRichText(rtv.getLastSnapshot())) }, textFieldStyle = defaultRichTextFieldStyle().copy( - placeholder = "Begin writing your Scene here", + placeholder = MR.strings.scene_editor_body_placeholder.get(), textColor = MaterialTheme.colorScheme.onBackground, placeholderColor = MaterialTheme.colorScheme.onBackground, textStyle = MaterialTheme.typography.bodyMedium, diff --git a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/scenelist/CreateDialog.kt b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/scenelist/CreateDialog.kt index 05b7007fa..fef1f420c 100644 --- a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/scenelist/CreateDialog.kt +++ b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/scenelist/CreateDialog.kt @@ -12,8 +12,10 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import com.darkrockstudios.apps.hammer.MR import com.darkrockstudios.apps.hammer.common.compose.MpDialog import com.darkrockstudios.apps.hammer.common.compose.Ui +import com.darkrockstudios.apps.hammer.common.compose.moko.get @ExperimentalMaterial3Api @Composable @@ -53,11 +55,11 @@ internal fun CreateDialog( horizontalArrangement = Arrangement.SpaceBetween ) { Button(onClick = { close(nameText) }) { - Text("Create") + Text(MR.strings.create_sceneitem_dialog_create_button.get()) } Button(onClick = { close(null) }) { - Text("Cancel") + Text(MR.strings.create_sceneitem_dialog_dismiss_button.get()) } } } diff --git a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/scenelist/GroupDeleteNotAllowedDialog.kt b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/scenelist/GroupDeleteNotAllowedDialog.kt index 9c03a0edf..bf5f070b2 100644 --- a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/scenelist/GroupDeleteNotAllowedDialog.kt +++ b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/scenelist/GroupDeleteNotAllowedDialog.kt @@ -9,8 +9,10 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.ExperimentalComposeApi import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import com.darkrockstudios.apps.hammer.MR import com.darkrockstudios.apps.hammer.common.compose.MpDialog import com.darkrockstudios.apps.hammer.common.compose.Ui +import com.darkrockstudios.apps.hammer.common.compose.moko.get import com.darkrockstudios.apps.hammer.common.data.SceneItem @ExperimentalMaterialApi @@ -20,7 +22,7 @@ internal fun GroupDeleteNotAllowedDialog(scene: SceneItem, dismissDialog: (Boole MpDialog( onCloseRequest = {}, visible = true, - title = "Cannot Delete Group" + title = MR.strings.group_cannot_delete_dialog_title.get() ) { Box(modifier = Modifier.fillMaxWidth().padding(Ui.Padding.XL)) { Column( @@ -30,7 +32,7 @@ internal fun GroupDeleteNotAllowedDialog(scene: SceneItem, dismissDialog: (Boole .padding(Ui.Padding.XL) ) { Text( - "You cannot delete a group while it still has children:\n\"${scene.name}\"", + MR.strings.group_cannot_delete_dialog_title.get(scene.name), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface ) @@ -38,7 +40,7 @@ internal fun GroupDeleteNotAllowedDialog(scene: SceneItem, dismissDialog: (Boole Spacer(modifier = Modifier.size(Ui.Padding.XL)) Button(onClick = { dismissDialog(false) }) { - Text("Okay") + Text(MR.strings.scene_delete_dialog_dismiss_button.get()) } } } diff --git a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/scenelist/SceneDeleteDialog.kt b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/scenelist/SceneDeleteDialog.kt index 5756b3cdc..301efcf50 100644 --- a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/scenelist/SceneDeleteDialog.kt +++ b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/scenelist/SceneDeleteDialog.kt @@ -9,18 +9,23 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.ExperimentalComposeApi import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import com.darkrockstudios.apps.hammer.MR import com.darkrockstudios.apps.hammer.common.compose.MpDialog import com.darkrockstudios.apps.hammer.common.compose.Ui +import com.darkrockstudios.apps.hammer.common.compose.moko.get +import com.darkrockstudios.apps.hammer.common.compose.rememberStrRes import com.darkrockstudios.apps.hammer.common.data.SceneItem @ExperimentalMaterialApi @ExperimentalComposeApi @Composable internal fun SceneDeleteDialog(scene: SceneItem, dismissDialog: (Boolean) -> Unit) { + val strRes = rememberStrRes() + MpDialog( onCloseRequest = {}, visible = true, - title = "Delete Scene" + title = MR.strings.scene_delete_dialog_title.get() ) { Box(modifier = Modifier.fillMaxWidth().padding(Ui.Padding.M)) { Column( @@ -30,7 +35,7 @@ internal fun SceneDeleteDialog(scene: SceneItem, dismissDialog: (Boolean) -> Uni .padding(Ui.Padding.XL) ) { Text( - "Are you sure you want to delete this scene:\n\"${scene.name}\"", + strRes.get(MR.strings.scene_delete_dialog_message, scene.name), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface ) @@ -42,10 +47,10 @@ internal fun SceneDeleteDialog(scene: SceneItem, dismissDialog: (Boolean) -> Uni horizontalArrangement = Arrangement.SpaceBetween ) { Button(onClick = { dismissDialog(true) }) { - Text("DELETE") + Text(MR.strings.scene_delete_dialog_delete_button.get()) } Button(onClick = { dismissDialog(false) }) { - Text("Dismiss") + Text(MR.strings.scene_delete_dialog_dismiss_button.get()) } } } diff --git a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/scenelist/SceneGroupItem.kt b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/scenelist/SceneGroupItem.kt index fb70460a0..537a0dad8 100644 --- a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/scenelist/SceneGroupItem.kt +++ b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/scenelist/SceneGroupItem.kt @@ -13,8 +13,10 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import com.darkrockstudios.apps.hammer.MR import com.darkrockstudios.apps.hammer.common.compose.Ui import com.darkrockstudios.apps.hammer.common.compose.bottomBorder +import com.darkrockstudios.apps.hammer.common.compose.moko.get import com.darkrockstudios.apps.hammer.common.data.SceneItem import com.darkrockstudios.apps.hammer.common.data.tree.TreeValue @@ -58,13 +60,13 @@ internal fun SceneGroupItem( if (collapsed) { Icon( imageVector = Icons.Filled.Folder, - contentDescription = "Group Collapsed", + contentDescription = MR.strings.scene_group_item_collapsed.get(), modifier = Modifier.size(24.dp).padding(end = Ui.Padding.M), ) } else { Icon( imageVector = Icons.Filled.FolderOpen, - contentDescription = "Group Expanded", + contentDescription = MR.strings.scene_group_item_expanded.get(), modifier = Modifier.size(24.dp).padding(end = Ui.Padding.M), ) } @@ -81,4 +83,4 @@ internal fun SceneGroupItem( } } } -} \ No newline at end of file +} diff --git a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/scenelist/SceneListUi.kt b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/scenelist/SceneListUi.kt index 26597d318..887646eee 100644 --- a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/scenelist/SceneListUi.kt +++ b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecteditor/scenelist/SceneListUi.kt @@ -15,8 +15,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import com.arkivanov.decompose.extensions.compose.jetbrains.subscribeAsState +import com.darkrockstudios.apps.hammer.MR import com.darkrockstudios.apps.hammer.common.components.projecteditor.scenelist.SceneList import com.darkrockstudios.apps.hammer.common.compose.Ui +import com.darkrockstudios.apps.hammer.common.compose.moko.get import com.darkrockstudios.apps.hammer.common.compose.rememberMainDispatcher import com.darkrockstudios.apps.hammer.common.data.SceneItem import com.darkrockstudios.apps.hammer.common.data.SceneSummary @@ -65,12 +67,20 @@ fun SceneListUi( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { - Text( - "\uD83D\uDCDD Scenes:", - style = MaterialTheme.typography.headlineSmall, - color = MaterialTheme.colorScheme.onBackground, - modifier = Modifier.weight(1f) - ) + Row { + Text( + "\uD83D\uDCDD ", + style = MaterialTheme.typography.headlineSmall, + color = MaterialTheme.colorScheme.onBackground, + modifier = Modifier.weight(1f) + ) + Text( + MR.strings.scene_list_header.get(), + style = MaterialTheme.typography.headlineSmall, + color = MaterialTheme.colorScheme.onBackground, + modifier = Modifier.weight(1f) + ) + } if (expandOrCollapse) { ElevatedButton(onClick = { @@ -123,20 +133,20 @@ fun SceneListUi( onClick = { showCreateGroupDialog = treeState.summary.sceneTree.root.value }, modifier = Modifier.padding(end = Ui.Padding.M) ) { - Icon(Icons.Filled.CreateNewFolder, "Create Group") + Icon(Icons.Filled.CreateNewFolder, MR.strings.scene_list_create_group_button.get()) } FloatingActionButton(onClick = { showCreateSceneDialog = treeState.summary.sceneTree.root.value }) { - Icon(Icons.Filled.PostAdd, "Create Scene") + Icon(Icons.Filled.PostAdd, MR.strings.scene_list_create_group_button.get()) } } } CreateDialog( show = showCreateGroupDialog != null, - title = "Create Group", - textLabel = "Group Name" + title = MR.strings.scene_list_create_group_dialog_title.get(), + textLabel = MR.strings.scene_list_create_group_dialog_message.get() ) { groupName -> scope.launch { Napier.d { "Create dialog close" } @@ -151,8 +161,8 @@ fun SceneListUi( CreateDialog( show = showCreateSceneDialog != null, - title = "Create Scene", - textLabel = "Scene Name" + title = MR.strings.scene_list_create_scene_dialog_title.get(), + textLabel = MR.strings.scene_list_create_scene_dialog_message.get() ) { sceneName -> scope.launch { Napier.d { "Create dialog close" } diff --git a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecthome/ProjectHomeUi.kt b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecthome/ProjectHomeUi.kt index 797df9389..889d6b065 100644 --- a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecthome/ProjectHomeUi.kt +++ b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projecthome/ProjectHomeUi.kt @@ -17,11 +17,14 @@ import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.arkivanov.decompose.extensions.compose.jetbrains.subscribeAsState +import com.darkrockstudios.apps.hammer.MR import com.darkrockstudios.apps.hammer.common.components.projecthome.ProjectHome import com.darkrockstudios.apps.hammer.common.compose.* +import com.darkrockstudios.apps.hammer.common.compose.moko.get import com.darkrockstudios.apps.hammer.common.data.encyclopediarepository.entry.EntryType import com.darkrockstudios.apps.hammer.common.util.formatDecimalSeparator import com.darkrockstudios.libraries.mpfilepicker.DirectoryPicker +import dev.icerock.moko.resources.compose.stringResource import io.github.koalaplot.core.bar.DefaultBarChartEntry import io.github.koalaplot.core.bar.VerticalBarChart import io.github.koalaplot.core.pie.BezierLabelConnector @@ -101,14 +104,14 @@ private fun Stats( Spacer(modifier = Modifier.size(Ui.Padding.XL)) Text( - "Created: ${state.created}", + stringResource(MR.strings.project_home_stat_created, state.created), style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurface ) Spacer(modifier = Modifier.size(Ui.Padding.XL)) Text( - "Stats:", + MR.strings.project_home_stat_header.get(), style = MaterialTheme.typography.headlineLarge, color = MaterialTheme.colorScheme.onSurface ) @@ -116,21 +119,21 @@ private fun Stats( } item { - NumericStatsBlock("Scenes", state.numberOfScenes) + NumericStatsBlock(MR.strings.project_home_stat_num_scenes.get(), state.numberOfScenes) } item { - NumericStatsBlock("Total Words", state.totalWords) + NumericStatsBlock(MR.strings.project_home_stat_total_words.get(), state.totalWords) } item { - GenericStatsBlock("Words in Chapters") { + GenericStatsBlock(MR.strings.project_home_stat_chapter_words.get()) { WordsInChaptersChart(state = state) } } item { - GenericStatsBlock("Encyclopedia Entries") { + GenericStatsBlock(MR.strings.project_home_stat_encyclopedia_entries.get()) { EncyclopediaChart(state = state) } } @@ -293,8 +296,8 @@ private fun WordsInChaptersChart( modifier = modifier.heightIn(64.dp, 196.dp).focusable(false), xAxisModel = CategoryAxisModel(xAxis), yAxisModel = LinearAxisModel(range = range), - xAxisTitle = "Chapter", - yAxisTitle = "Words", + xAxisTitle = MR.strings.project_home_stat_chapter_words_x_axis.get(), + yAxisTitle = MR.strings.project_home_stat_chapter_words_y_axis.get(), xAxisLabels = { index -> (index + 1).toString() }, xAxisStyle = rememberAxisStyle(color = MaterialTheme.colorScheme.onBackground), yAxisLabels = { it.toInt().toString() }, @@ -311,37 +314,51 @@ private fun Actions( scope: CoroutineScope, snackbarHostState: SnackbarHostState ) { + val strRes = rememberStrRes() + val defaultDispatcher = rememberDefaultDispatcher() val state by component.state.subscribeAsState() + var toastMessage: String? = remember { null } + + LaunchedEffect(toastMessage) { + toastMessage?.let { message -> + if (message.isNotBlank()) { + scope.launch { + snackbarHostState.showSnackbar(message) + } + } + } + } + Column(modifier = modifier.padding(Ui.Padding.XL)) { Text( - "Actions:", + MR.strings.project_home_actions_header.get(), style = MaterialTheme.typography.headlineLarge, color = MaterialTheme.colorScheme.onSurface ) Spacer(modifier = Modifier.size(Ui.Padding.XL)) Button(onClick = component::beginProjectExport) { - Text("Export Story") + Text(MR.strings.project_home_action_export.get()) } if (state.hasServer) { Spacer(modifier = Modifier.size(Ui.Padding.XL)) Button(onClick = component::startProjectSync) { - Text("Sync Story") + Text(MR.strings.project_home_action_sync.get()) } } if (component.supportsBackup()) { Spacer(modifier = Modifier.size(Ui.Padding.XL)) Button(onClick = { component.createBackup { backup -> - if (backup != null) { - scope.launch { snackbarHostState.showSnackbar("Backup Created: ${backup.path.name}") } + toastMessage = if (backup != null) { + strRes.get(MR.strings.project_home_action_backup_toast_success, backup.path.name) } else { - scope.launch { snackbarHostState.showSnackbar("Failed to create backup!") } + strRes.get(MR.strings.project_home_action_backup_toast_failure) } } }) { - Text("Backup") + Text(MR.strings.project_home_action_backup.get()) } } } @@ -350,7 +367,7 @@ private fun Actions( if (path != null) { scope.launch(defaultDispatcher) { component.exportProject(path) - snackbarHostState.showSnackbar("Story Exported") + snackbarHostState.showSnackbar(strRes.get(MR.strings.project_home_action_export_toast_success)) } } else { component.endProjectExport() diff --git a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projectselection/AccountSettingsUi.kt b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projectselection/AccountSettingsUi.kt index b89ddb71d..e56c77385 100644 --- a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projectselection/AccountSettingsUi.kt +++ b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projectselection/AccountSettingsUi.kt @@ -18,6 +18,7 @@ import com.darkrockstudios.apps.hammer.common.compose.moko.get import com.darkrockstudios.apps.hammer.common.data.globalsettings.UiTheme import com.darkrockstudios.apps.hammer.common.getDataVersion import com.darkrockstudios.libraries.mpfilepicker.DirectoryPicker +import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @@ -71,7 +72,7 @@ internal fun AccountSettingsUi( } Text( - "Data: v${getDataVersion()}", + stringResource(MR.strings.settings_data_version, getDataVersion()), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onBackground, ) diff --git a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projectselection/ProjectListUi.kt b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projectselection/ProjectListUi.kt index b8cbc1587..b8d4e789d 100644 --- a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projectselection/ProjectListUi.kt +++ b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projectselection/ProjectListUi.kt @@ -30,10 +30,10 @@ import com.darkrockstudios.apps.hammer.common.compose.MpScrollBarList import com.darkrockstudios.apps.hammer.common.compose.SimpleConfirm import com.darkrockstudios.apps.hammer.common.compose.Ui import com.darkrockstudios.apps.hammer.common.compose.moko.get -import com.darkrockstudios.apps.hammer.common.compose.moko.getString import com.darkrockstudios.apps.hammer.common.data.ProjectDef import com.darkrockstudios.apps.hammer.common.util.format import com.soywiz.korio.async.launch +import dev.icerock.moko.resources.compose.stringResource import kotlinx.datetime.Instant import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime @@ -49,11 +49,11 @@ fun ProjectListUi( var showProjectCreate by remember { mutableStateOf(false) } val snackbarHostState = remember { SnackbarHostState() } - LaunchedEffect(state.toast) { + val toastMessage = state.toast?.get() + LaunchedEffect(toastMessage) { scope.launch { - val message = state.toast - if (message?.isNotEmpty() == true) { - snackbarHostState.showSnackbar(message) + if (toastMessage?.isNotEmpty() == true) { + snackbarHostState.showSnackbar(toastMessage) } } } @@ -83,7 +83,7 @@ fun ProjectListUi( Button(onClick = { component.showProjectsSync() }) { - Image(Icons.Default.Refresh, "Sync Projects") + Image(Icons.Default.Refresh, MR.strings.projects_list_sync_button.get()) } } } @@ -128,7 +128,7 @@ fun ProjectListUi( onClick = { showProjectCreate = true }, modifier = Modifier.align(Alignment.BottomEnd).padding(Ui.Padding.M), ) { - Icon(imageVector = Icons.Filled.Create, "Create Project") + Icon(imageVector = Icons.Filled.Create, MR.strings.projects_list_create_button.get()) } SnackbarHost(snackbarHostState, modifier = Modifier.align(Alignment.BottomCenter)) @@ -141,7 +141,7 @@ fun ProjectListUi( projectDefDeleteTarget?.let { project -> SimpleConfirm( title = MR.strings.delete_project_title.get(), - message = getString(MR.strings.delete_project_message, project.name), + message = stringResource(MR.strings.delete_project_message, project.name), onDismiss = { projectDefDeleteTarget = null }, onConfirm = { component.deleteProject(project) diff --git a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projectselection/ProjectsSyncDialog.kt b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projectselection/ProjectsSyncDialog.kt index 0be6abaa7..6456fa614 100644 --- a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projectselection/ProjectsSyncDialog.kt +++ b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projectselection/ProjectsSyncDialog.kt @@ -16,10 +16,12 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import com.arkivanov.decompose.extensions.compose.jetbrains.subscribeAsState +import com.darkrockstudios.apps.hammer.MR import com.darkrockstudios.apps.hammer.common.components.projectselection.projectslist.ProjectsList import com.darkrockstudios.apps.hammer.common.compose.LocalScreenCharacteristic import com.darkrockstudios.apps.hammer.common.compose.MpDialog import com.darkrockstudios.apps.hammer.common.compose.Ui +import com.darkrockstudios.apps.hammer.common.compose.moko.get import kotlinx.coroutines.launch @Composable @@ -34,7 +36,7 @@ fun ProjectsSyncDialog(component: ProjectsList) { }, visible = state.syncState.showProjectSync, size = DpSize(400.dp, 400.dp), - title = "Synchronization" + title = MR.strings.account_sync_dialog_title.get() ) { ProjectsSyncDialogContents(component) } @@ -53,7 +55,7 @@ internal fun ProjectsSyncDialogContents( verticalAlignment = CenterVertically ) { Text( - "Account Sync", + MR.strings.account_sync_dialog_header.get(), style = MaterialTheme.typography.headlineSmall ) @@ -62,7 +64,7 @@ internal fun ProjectsSyncDialogContents( if (!state.syncState.syncComplete) { Icon( Icons.Default.Cancel, - contentDescription = "Cancel", + contentDescription = MR.strings.account_sync_dialog_cancel_button.get(), modifier = Modifier.padding(Ui.Padding.S).clickable { component.cancelProjectsSync() }, tint = MaterialTheme.colorScheme.onBackground ) @@ -113,7 +115,7 @@ fun SyncLog(component: ProjectsList, onClose: () -> Unit) { MpDialog( onCloseRequest = onClose, visible = true, - title = "Sync Log", + title = MR.strings.account_sync_log_title.get(), ) { SyncLogContents(component) } @@ -180,7 +182,7 @@ private fun ProjectStatusIcon( ProjectsList.Status.Pending -> { Icon( Icons.Default.HourglassTop, - contentDescription = "Pending", + contentDescription = MR.strings.account_sync_dialog_status_pending.get(), modifier = modifier ) } @@ -192,7 +194,7 @@ private fun ProjectStatusIcon( ProjectsList.Status.Failed -> { Icon( Icons.Default.Error, - contentDescription = "Error", + contentDescription = MR.strings.account_sync_dialog_status_error.get(), tint = MaterialTheme.colorScheme.error, modifier = modifier ) @@ -201,7 +203,7 @@ private fun ProjectStatusIcon( ProjectsList.Status.Complete -> { Icon( Icons.Default.CheckCircle, - contentDescription = "Sync Complete", + contentDescription = MR.strings.account_sync_dialog_status_pending.get(), tint = Color.Green, modifier = modifier ) @@ -210,7 +212,7 @@ private fun ProjectStatusIcon( ProjectsList.Status.Canceled -> { Icon( Icons.Default.Cancel, - contentDescription = "Canceled", + contentDescription = MR.strings.account_sync_dialog_status_canceled.get(), modifier = modifier ) } diff --git a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projectselection/ServerSettingsUi.kt b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projectselection/ServerSettingsUi.kt index 2dce66954..4c254cc94 100644 --- a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projectselection/ServerSettingsUi.kt +++ b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projectselection/ServerSettingsUi.kt @@ -14,6 +14,7 @@ import com.darkrockstudios.apps.hammer.common.components.projectselection.accoun import com.darkrockstudios.apps.hammer.common.compose.SimpleConfirm import com.darkrockstudios.apps.hammer.common.compose.Ui import com.darkrockstudios.apps.hammer.common.compose.moko.get +import com.darkrockstudios.apps.hammer.common.compose.rememberStrRes import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -21,6 +22,7 @@ import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @Composable fun ServerSettingsUi(component: AccountSettings, scope: CoroutineScope, snackbarHostState: SnackbarHostState) { + val strRes = rememberStrRes() val state by component.state.subscribeAsState() var showConfirmRemoveServer by rememberSaveable { mutableStateOf(false) } @@ -54,7 +56,7 @@ fun ServerSettingsUi(component: AccountSettings, scope: CoroutineScope, snackbar ) Text( - state.serverUrl ?: "error", + state.serverUrl ?: MR.strings.settings_server_unknown_error.get(), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onBackground, fontStyle = FontStyle.Italic @@ -69,7 +71,7 @@ fun ServerSettingsUi(component: AccountSettings, scope: CoroutineScope, snackbar ) Text( - state.serverEmail ?: "error", + state.serverEmail ?: MR.strings.settings_server_unknown_error.get(), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onBackground, fontStyle = FontStyle.Italic @@ -86,7 +88,7 @@ fun ServerSettingsUi(component: AccountSettings, scope: CoroutineScope, snackbar } ) Text( - "Auto-Sync", + MR.strings.settings_server_auto_sync.get(), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onBackground, modifier = Modifier.align(Alignment.CenterVertically) @@ -105,7 +107,7 @@ fun ServerSettingsUi(component: AccountSettings, scope: CoroutineScope, snackbar } ) Text( - "Backup on sync", + MR.strings.settings_server_sync_backup.get(), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onBackground, modifier = Modifier.align(Alignment.CenterVertically) @@ -124,7 +126,7 @@ fun ServerSettingsUi(component: AccountSettings, scope: CoroutineScope, snackbar } ) Text( - "Close Sync Dialogs on Success", + MR.strings.settings_server_sync_auto_close.get(), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onBackground, modifier = Modifier.align(Alignment.CenterVertically) @@ -144,7 +146,7 @@ fun ServerSettingsUi(component: AccountSettings, scope: CoroutineScope, snackbar scope.launch { component.setMaxBackups(value) } } }, - label = { Text("Max Backups per Project") }, + label = { Text(MR.strings.settings_server_max_backups.get()) }, ) Spacer(modifier = Modifier.size(Ui.Padding.L)) @@ -152,18 +154,16 @@ fun ServerSettingsUi(component: AccountSettings, scope: CoroutineScope, snackbar Button(onClick = { scope.launch { if (component.authTest()) { - snackbarHostState.showSnackbar("Auth Test Successful") + snackbarHostState.showSnackbar(strRes.get(MR.strings.settings_server_authtest_toast_success)) } else { - snackbarHostState.showSnackbar("Auth Test Failed") + snackbarHostState.showSnackbar(strRes.get(MR.strings.settings_server_authtest_toast_failure)) } } }) { - //Text(MR.strings.settings_server_modify_button.get()) - Text("Test Auth") + Text(MR.strings.settings_server_test_auth_button.get()) } Button(onClick = { component.reauthenticate() }) { - //Text(MR.strings.settings_server_modify_button.get()) - Text("Re-Auth") + Text(MR.strings.settings_server_reauth_button.get()) } Spacer(modifier = Modifier.size(Ui.Padding.L)) @@ -171,16 +171,15 @@ fun ServerSettingsUi(component: AccountSettings, scope: CoroutineScope, snackbar Button(onClick = { showConfirmRemoveServer = true }) { - //Text(MR.strings.settings_server_modify_button.get()) - Text("Remove Server") + Text(MR.strings.settings_server_remove_server_button.get()) } } } if (showConfirmRemoveServer) { SimpleConfirm( - title = "Remove Server", - message = "Are you sure you want to remove the server?", + title = MR.strings.settings_remove_server_dialog_title.get(), + message = MR.strings.settings_remove_server_dialog_message.get(), onDismiss = { showConfirmRemoveServer = false }, onConfirm = { scope.launch { diff --git a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projectselection/ServerSetupDialog.kt b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projectselection/ServerSetupDialog.kt index 38b19f814..b8067c9ae 100644 --- a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projectselection/ServerSetupDialog.kt +++ b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projectselection/ServerSetupDialog.kt @@ -85,7 +85,7 @@ fun ServerSetupDialog( Column(modifier = Modifier.verticalScroll(rememberScrollState())) { Text( - "Configure Server", + MR.strings.settings_server_setup_header.get(), style = MaterialTheme.typography.headlineMedium ) @@ -156,7 +156,10 @@ fun ServerSetupDialog( else Icons.Filled.VisibilityOff // Please provide localized description for accessibility services - val description = if (passwordVisible) "Hide password" else "Show password" + val description = if (passwordVisible) + MR.strings.settings_server_setup_password_hide.get() + else + MR.strings.settings_server_setup_password_show.get() IconButton(onClick = { passwordVisible = !passwordVisible }) { Icon(imageVector = image, description) @@ -236,8 +239,8 @@ fun ServerSetupDialog( } SimpleConfirm( - title = "Remove Local Content?", - message = "Should we remove your current local content before syncing? If not, we will attempt to merge your local content with the server content. If the content is unrelated, it could cause problems.", + title = MR.strings.remove_local_dialog_title.get(), + message = MR.strings.remove_local_dialog_message.get(), implicitCancel = false, onDismiss = { setupServer(create, false) diff --git a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projectsync/EncyclopediaEntryConflict.kt b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projectsync/EncyclopediaEntryConflict.kt index b92aeadb4..d7199cc8a 100644 --- a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projectsync/EncyclopediaEntryConflict.kt +++ b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projectsync/EncyclopediaEntryConflict.kt @@ -11,8 +11,10 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import com.darkrockstudios.apps.hammer.MR import com.darkrockstudios.apps.hammer.common.components.projectsync.ProjectSync import com.darkrockstudios.apps.hammer.common.compose.Ui +import com.darkrockstudios.apps.hammer.common.compose.moko.get @OptIn(ExperimentalLayoutApi::class, ExperimentalMaterial3Api::class) @Composable @@ -30,13 +32,13 @@ internal fun EncyclopediaEntryConflict( Row { Icon( Icons.Default.Warning, - contentDescription = "Conflict", + contentDescription = MR.strings.sync_conflict.get(), modifier = Modifier.size(32.dp).align(Alignment.CenterVertically), tint = MaterialTheme.colorScheme.error ) Text( - text = "Encyclopedia Entry Conflict:", + text = MR.strings.sync_conflict_encyclopedia.get(), style = MaterialTheme.typography.headlineSmall, modifier = Modifier.padding(start = Ui.Padding.L).align(Alignment.CenterVertically) ) @@ -53,11 +55,11 @@ internal fun EncyclopediaEntryConflict( verticalAlignment = Alignment.CenterVertically ) { Text( - text = "Local Entry:", + text = MR.strings.sync_conflict_local_entry.get(), style = MaterialTheme.typography.headlineSmall ) Button(onClick = { component.resolveConflict(entityConflict.clientEntity) }) { - Text("Use Local") + Text(MR.strings.sync_conflict_local_use_button.get()) } } SelectionContainer { @@ -68,7 +70,10 @@ internal fun EncyclopediaEntryConflict( } Text(entityConflict.serverEntity.entryType) Text( - if (entityConflict.serverEntity.image != null) "Has Image" else "No Image", + if (entityConflict.serverEntity.image != null) + MR.strings.sync_conflict_encyclopedia_has_image.get() + else + MR.strings.sync_conflict_encyclopedia_no_image.get(), style = MaterialTheme.typography.bodyLarge ) SelectionContainer { @@ -91,11 +96,11 @@ internal fun EncyclopediaEntryConflict( verticalAlignment = Alignment.CenterVertically ) { Text( - text = "Remote Entry:", + text = MR.strings.sync_conflict_remote_entry.get(), style = MaterialTheme.typography.headlineSmall ) Button(onClick = { component.resolveConflict(entityConflict.serverEntity) }) { - Text("Use Remote") + Text(MR.strings.sync_conflict_remote_use_button.get()) } } SelectionContainer { @@ -106,7 +111,10 @@ internal fun EncyclopediaEntryConflict( } Text(entityConflict.serverEntity.entryType) Text( - if (entityConflict.serverEntity.image != null) "Has Image" else "No Image", + if (entityConflict.serverEntity.image != null) + MR.strings.sync_conflict_encyclopedia_has_image.get() + else + MR.strings.sync_conflict_encyclopedia_no_image.get(), style = MaterialTheme.typography.bodyLarge ) SelectionContainer { @@ -124,33 +132,22 @@ internal fun EncyclopediaEntryConflict( } } - - - - - - - - - - - Column { - Text("Encyclopedia Entry Conflict") + Text(MR.strings.sync_conflict_encyclopedia_title.get()) Spacer(modifier = Modifier.size(Ui.Padding.L)) Row(modifier = Modifier.fillMaxSize()) { Column(modifier = Modifier.weight(1f)) { - Text("Local Event: ${entityConflict.clientEntity.name}") + Text(MR.strings.sync_conflict_encyclopedia_local.get(entityConflict.clientEntity.name)) Button(onClick = { component.resolveConflict(entityConflict.clientEntity) }) { - Text("Use Local") + Text(MR.strings.sync_conflict_local_use_button.get()) } Text(entityConflict.clientEntity.text) } Column(modifier = Modifier.weight(1f)) { - Text("Remote Event: ${entityConflict.serverEntity.name}") + Text(MR.strings.sync_conflict_encyclopedia_local.get(entityConflict.serverEntity.name)) Button(onClick = { component.resolveConflict(entityConflict.serverEntity) }) { - Text("Use Remote") + Text(MR.strings.sync_conflict_remote_use_button.get()) } Text(entityConflict.serverEntity.text) } diff --git a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projectsync/NoteConflict.kt b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projectsync/NoteConflict.kt index d97a1099a..c8ca74d3e 100644 --- a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projectsync/NoteConflict.kt +++ b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/projectsync/NoteConflict.kt @@ -14,8 +14,10 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import com.darkrockstudios.apps.hammer.MR import com.darkrockstudios.apps.hammer.common.components.projectsync.ProjectSync import com.darkrockstudios.apps.hammer.common.compose.Ui +import com.darkrockstudios.apps.hammer.common.compose.moko.get @Composable internal fun NoteConflict( @@ -32,7 +34,7 @@ internal fun NoteConflict( Row { Icon( Icons.Default.Warning, - contentDescription = "Conflict", + contentDescription = MR.strings.sync_conflict.get(), modifier = Modifier.size(32.dp).align(Alignment.CenterVertically), tint = MaterialTheme.colorScheme.error ) diff --git a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/timeline/CreateTimeLineEventUi.kt b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/timeline/CreateTimeLineEventUi.kt index af8d898b0..2d02ac27a 100644 --- a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/timeline/CreateTimeLineEventUi.kt +++ b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/timeline/CreateTimeLineEventUi.kt @@ -11,9 +11,12 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment.Companion.End import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import com.darkrockstudios.apps.hammer.MR import com.darkrockstudios.apps.hammer.common.components.timeline.CreateTimeLineEvent import com.darkrockstudios.apps.hammer.common.compose.LocalScreenCharacteristic import com.darkrockstudios.apps.hammer.common.compose.Ui +import com.darkrockstudios.apps.hammer.common.compose.moko.get +import com.darkrockstudios.apps.hammer.common.compose.rememberStrRes import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -26,6 +29,8 @@ fun CreateTimeLineEventUi( snackbarHostState: SnackbarHostState, close: () -> Unit ) { + val strRes = rememberStrRes() + var dateText by remember { mutableStateOf("") } var contentText by remember { mutableStateOf("") } @@ -40,40 +45,40 @@ fun CreateTimeLineEventUi( ) { Icon( Icons.Default.Close, - "Close", + MR.strings.timeline_create_close_button.get(), tint = MaterialTheme.colorScheme.onBackground ) } } Text( - "Time Line Event", + MR.strings.timeline_create_title.get(), style = MaterialTheme.typography.headlineLarge, color = MaterialTheme.colorScheme.onBackground ) TextField( value = dateText, onValueChange = { dateText = it }, - label = { Text("Date (optional)") }, + label = { Text(MR.strings.timeline_create_date_label.get()) }, singleLine = true ) OutlinedTextField( modifier = Modifier.fillMaxWidth().height(128.dp), value = contentText, onValueChange = { contentText = it }, - label = { Text("Content") }, + label = { Text(MR.strings.timeline_create_content_label.get()) }, ) Button(onClick = { scope.launch { if (component.createEvent(dateText, contentText)) { - launch { snackbarHostState.showSnackbar("Event Created") } + launch { snackbarHostState.showSnackbar(strRes.get(MR.strings.timeline_create_toast_success)) } close() } else { - launch { snackbarHostState.showSnackbar("Failed to create event") } + launch { snackbarHostState.showSnackbar(strRes.get(MR.strings.timeline_create_toast_failure)) } } } }) { - Text("Create") + Text(MR.strings.timeline_create_create_event_button.get()) } } } \ No newline at end of file diff --git a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/timeline/TimeLineOverviewUi.kt b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/timeline/TimeLineOverviewUi.kt index cd58f2d53..f8c9c0753 100644 --- a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/timeline/TimeLineOverviewUi.kt +++ b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/timeline/TimeLineOverviewUi.kt @@ -20,8 +20,10 @@ import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.arkivanov.decompose.extensions.compose.jetbrains.subscribeAsState +import com.darkrockstudios.apps.hammer.MR import com.darkrockstudios.apps.hammer.common.components.timeline.TimeLineOverview import com.darkrockstudios.apps.hammer.common.compose.Ui +import com.darkrockstudios.apps.hammer.common.compose.moko.get import com.darkrockstudios.apps.hammer.common.compose.reorderable.DragDropList import com.darkrockstudios.apps.hammer.common.data.timelinerepository.TimeLineEvent import kotlinx.coroutines.CoroutineScope @@ -42,7 +44,7 @@ fun TimeLineOverviewUi( Box(modifier = Modifier.fillMaxSize().padding(Ui.Padding.XL)) { Column(modifier = Modifier.widthIn(0.dp, 700.dp).fillMaxWidth()) { Text( - "Time Line", + MR.strings.timeline_title.get(), style = MaterialTheme.typography.headlineLarge, color = MaterialTheme.colorScheme.onBackground ) @@ -50,7 +52,7 @@ fun TimeLineOverviewUi( val events = state.timeLine?.events ?: emptyList() if (events.isEmpty()) { Text( - "No Events", + MR.strings.timeline_no_events.get(), modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colorScheme.onBackground, @@ -76,7 +78,7 @@ fun TimeLineOverviewUi( onClick = showCreate, modifier = Modifier.align(Alignment.BottomEnd).testTag(TIME_LINE_CREATE_TAG) ) { - Icon(Icons.Default.Create, "Create Event") + Icon(Icons.Default.Create, MR.strings.timeline_create_event_button.get()) } } } diff --git a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/timeline/ViewTimeLineEventUi.kt b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/timeline/ViewTimeLineEventUi.kt index 854a07a14..d920e5bf5 100644 --- a/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/timeline/ViewTimeLineEventUi.kt +++ b/composeUi/src/commonMain/kotlin/com/darkrockstudios/apps/hammer/common/timeline/ViewTimeLineEventUi.kt @@ -16,8 +16,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.arkivanov.decompose.extensions.compose.jetbrains.subscribeAsState +import com.darkrockstudios.apps.hammer.MR import com.darkrockstudios.apps.hammer.common.components.timeline.ViewTimeLineEvent import com.darkrockstudios.apps.hammer.common.compose.* +import com.darkrockstudios.apps.hammer.common.compose.moko.get import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -31,6 +33,8 @@ fun ViewTimeLineEventUi( snackbarHostState: SnackbarHostState, closeEvent: () -> Unit ) { + val strRes = rememberStrRes() + val dispatcherMain = rememberMainDispatcher() val dispatcherDefault = rememberDefaultDispatcher() val state by component.state.subscribeAsState() @@ -73,13 +77,13 @@ fun ViewTimeLineEventUi( } scope.launch { - snackbarHostState.showSnackbar("Entry Saved") + snackbarHostState.showSnackbar(strRes.get(MR.strings.timeline_view_toast_save_success)) } } }) { Icon( Icons.Filled.Check, - "Save", + MR.strings.timeline_view_save_button.get(), tint = MaterialTheme.colorScheme.onSurface ) } @@ -91,7 +95,7 @@ fun ViewTimeLineEventUi( }) { Icon( Icons.Filled.Cancel, - "Cancel", + MR.strings.timeline_view_cancel_button.get(), tint = MaterialTheme.colorScheme.error ) } @@ -110,8 +114,8 @@ fun ViewTimeLineEventUi( if (discardConfirm) { SimpleConfirm( - title = "Discard Changes?", - message = "You will lose any changes you have made.", + title = MR.strings.timeline_view_discard_title.get(), + message = MR.strings.timeline_view_discard_message.get(), onDismiss = { discardConfirm = false } ) { eventDateText = event.date ?: "" @@ -136,7 +140,7 @@ fun ViewTimeLineEventUi( ) { Icon( Icons.Filled.Close, - contentDescription = "Close Entry", + contentDescription = MR.strings.timeline_view_close_button.get(), tint = MaterialTheme.colorScheme.onSurface ) } @@ -150,7 +154,7 @@ fun ViewTimeLineEventUi( modifier = Modifier.wrapContentHeight().fillMaxWidth(), value = eventDateText, onValueChange = { eventDateText = it }, - placeholder = { Text("Date") } + placeholder = { Text(MR.strings.timeline_view_date_label.get()) } ) } else { Text( @@ -169,7 +173,7 @@ fun ViewTimeLineEventUi( value = eventText, onValueChange = { eventText = it }, modifier = Modifier.fillMaxWidth().padding(PaddingValues(bottom = Ui.Padding.XL)), - placeholder = { Text(text = "Describe your event") }, + placeholder = { Text(text = MR.strings.timeline_view_content_placeholder.get()) }, maxLines = 10, ) } else { @@ -186,8 +190,8 @@ fun ViewTimeLineEventUi( if (closeConfirm) { SimpleConfirm( - title = "Discard Changes?", - message = "You will lose any changes you have made.", + title = MR.strings.timeline_view_discard_title.get(), + message = MR.strings.timeline_view_discard_message.get(), onDismiss = { closeConfirm = false } ) { closeConfirm = false diff --git a/desktop/build.gradle.kts b/desktop/build.gradle.kts index 57ff7e61e..8037deb6f 100644 --- a/desktop/build.gradle.kts +++ b/desktop/build.gradle.kts @@ -3,12 +3,14 @@ import org.jetbrains.compose.desktop.application.dsl.TargetFormat val app_version: String by extra val data_version: String by extra val jvm_version: String by extra +val moko_resources_version: String by extra plugins { - kotlin("multiplatform") - kotlin("plugin.serialization") - id("org.jetbrains.compose") - id("org.jetbrains.kotlinx.kover") + kotlin("multiplatform") + kotlin("plugin.serialization") + id("org.jetbrains.compose") + id("org.jetbrains.kotlinx.kover") + id("dev.icerock.mobile.multiplatform-resources") } group = "com.darkrockstudios.apps.hammer.desktop" @@ -16,49 +18,72 @@ version = app_version kotlin { - jvm { - compilations.all { - kotlinOptions.jvmTarget = jvm_version - } - withJava() - } - sourceSets { - val jvmMain by getting { - dependencies { - implementation(project(":base")) - implementation(project(":common")) - implementation(project(":composeUi")) - implementation(compose.preview) - implementation(compose.desktop.currentOs) - implementation("com.github.weisj:darklaf-core:3.0.2") - implementation("org.jetbrains.kotlinx:kotlinx-cli:0.3.5") - } - } - val jvmTest by getting - } + jvm { + compilations.all { + kotlinOptions.jvmTarget = jvm_version + } + withJava() + } + sourceSets { + val commonMain by getting { + resources.srcDirs("resources") + dependencies { + implementation("dev.icerock.moko:resources:$moko_resources_version") + } + } + val jvmMain by getting { + dependencies { + implementation(project(":base")) + implementation(project(":common")) + implementation(project(":composeUi")) + implementation(compose.preview) + implementation(compose.desktop.currentOs) + implementation("com.github.weisj:darklaf-core:3.0.2") + implementation("org.jetbrains.kotlinx:kotlinx-cli:0.3.5") + } + } + val jvmTest by getting + } } compose.desktop { - application { - mainClass = "com.darkrockstudios.apps.hammer.desktop.MainKt" - nativeDistributions { - targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) - includeAllModules = true - packageName = "hammer" - packageVersion = app_version - outputBaseDir.set(project.buildDir.resolve("installers")) + application { + mainClass = "com.darkrockstudios.apps.hammer.desktop.MainKt" + nativeDistributions { + targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) + includeAllModules = true + packageName = "hammer" + packageVersion = app_version + description = "A simple tool for building stories." + copyright = "© 2023 Adam W. Brown, All rights reserved." + licenseFile.set(project.file("../LICENSE")) + outputBaseDir.set(project.buildDir.resolve("installers")) - windows.apply { - menuGroup = "Hammer" - shortcut = true - console = true - } - } - jvmArgs("-Dcompose.application.configure.swing.globals=false") + windows.apply { + menuGroup = "Hammer" + shortcut = true + console = true + } - buildTypes.release.proguard { - isEnabled.set(false) - configurationFiles.from("proguard-rules.pro") - } - } + linux.apply { + rpmLicenseType = "MIT" + } + + macOS.apply { + appStore = false + } + } + jvmArgs("-Dcompose.application.configure.swing.globals=false") + + buildTypes.release.proguard { + isEnabled.set(false) + configurationFiles.from("proguard-rules.pro") + } + } +} + +multiplatformResources { + multiplatformResourcesClassName = "DR" + multiplatformResourcesPackage = "com.darkrockstudios.apps.hammer.desktop" + multiplatformResourcesSourceSet = "commonMain" } \ No newline at end of file diff --git a/desktop/resources/MR/base/strings.xml b/desktop/resources/MR/base/strings.xml new file mode 100644 index 000000000..606aaf06b --- /dev/null +++ b/desktop/resources/MR/base/strings.xml @@ -0,0 +1,15 @@ + + + Unsaved Scenes + Save unsaved scenes? + Discard and close + Cancel + Save and close + + Hammer - Project Selection + + Hammer - %1$s + File + Close Project + Exit + \ No newline at end of file diff --git a/desktop/resources/MR/de/strings.xml b/desktop/resources/MR/de/strings.xml new file mode 100644 index 000000000..e4aeebff8 --- /dev/null +++ b/desktop/resources/MR/de/strings.xml @@ -0,0 +1,13 @@ + + + Nicht gespeicherte Szenen + Ungespeicherte Szenen speichern\? + Verwerfen und schließen + Abbrechen + Speichern und schließen + Hammer - Projektauswahl + Hammer - %1$s + Datei + Projekt schließen + Beenden + \ No newline at end of file diff --git a/desktop/resources/MR/es/strings.xml b/desktop/resources/MR/es/strings.xml new file mode 100644 index 000000000..6fda341ac --- /dev/null +++ b/desktop/resources/MR/es/strings.xml @@ -0,0 +1,12 @@ + + + Escenas sin guardar + ¿Desea guardar las escenas sin guardar\? + Descartar y cerrar + Cancelar + Guardar y cerrar + Archivo + Hammer - %1$s + Salir + Cerrar proyecto + \ No newline at end of file diff --git a/desktop/resources/MR/fr/strings.xml b/desktop/resources/MR/fr/strings.xml new file mode 100644 index 000000000..64f8ecdfc --- /dev/null +++ b/desktop/resources/MR/fr/strings.xml @@ -0,0 +1,13 @@ + + + Rejeter et fermer + Annuler + Enregistrer et fermer + Hammer - Sélection du projet + Hammer – %1$s + Fichier + Fermer le projet + Quitter + Scènes non enregistrées + Enregistrer des scènes non enregistrées \? + \ No newline at end of file diff --git a/desktop/resources/MR/nb-rNO/strings.xml b/desktop/resources/MR/nb-rNO/strings.xml new file mode 100644 index 000000000..0d2c4cc40 --- /dev/null +++ b/desktop/resources/MR/nb-rNO/strings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/desktop/resources/MR/uk/strings.xml b/desktop/resources/MR/uk/strings.xml new file mode 100644 index 000000000..f442bf92e --- /dev/null +++ b/desktop/resources/MR/uk/strings.xml @@ -0,0 +1,13 @@ + + + Незбережені сцени + Зберегти незбережені сцени\? + Відхилити та закрити + Скасувати + Зберегти та закрити + Hammer - Вибір проєктів + Hammer - %1$s + Файл + Закрити проєкт + Закрити + \ No newline at end of file diff --git a/desktop/resources/MR/zh-rCN/strings.xml b/desktop/resources/MR/zh-rCN/strings.xml new file mode 100644 index 000000000..a7c715f05 --- /dev/null +++ b/desktop/resources/MR/zh-rCN/strings.xml @@ -0,0 +1,13 @@ + + + 保存未保存的场景\? + 不保存退出 + 保存并退出 + Hammer - 项目选择 + Hammer - %1$s + 文件 + 关闭项目 + 未保存的场景 + 取消 + 退出 + \ No newline at end of file diff --git a/desktop/src/jvmMain/kotlin/com/darkrockstudios/apps/hammer/desktop/Main.kt b/desktop/src/jvmMain/kotlin/com/darkrockstudios/apps/hammer/desktop/Main.kt index 6b8d2c100..6f085562f 100644 --- a/desktop/src/jvmMain/kotlin/com/darkrockstudios/apps/hammer/desktop/Main.kt +++ b/desktop/src/jvmMain/kotlin/com/darkrockstudios/apps/hammer/desktop/Main.kt @@ -21,6 +21,7 @@ import com.darkrockstudios.apps.hammer.common.AppCloseManager import com.darkrockstudios.apps.hammer.common.compose.Ui import com.darkrockstudios.apps.hammer.common.compose.getDefaultDispatcher import com.darkrockstudios.apps.hammer.common.compose.getMainDispatcher +import com.darkrockstudios.apps.hammer.common.compose.moko.get import com.darkrockstudios.apps.hammer.common.compose.theme.AppTheme import com.darkrockstudios.apps.hammer.common.data.globalsettings.GlobalSettingsRepository import com.darkrockstudios.apps.hammer.common.data.globalsettings.UiTheme @@ -130,13 +131,13 @@ fun main(args: Array) { LocalImageLoader provides imageLoader, ) { when (val windowState = applicationState.windows.value) { - is com.darkrockstudios.apps.hammer.desktop.WindowState.ProjectSectionWindow -> { + is WindowState.ProjectSectionWindow -> { ProjectSelectionWindow { project -> applicationState.openProject(project) } } - is com.darkrockstudios.apps.hammer.desktop.WindowState.ProjectWindow -> { + is WindowState.ProjectWindow -> { ProjectEditorWindow(applicationState, windowState.projectDef) } } @@ -155,15 +156,15 @@ internal fun confirmCloseDialog( dismissDialog: (ConfirmCloseResult, ApplicationState.CloseType) -> Unit ) { AlertDialog( - title = { Text("Unsaved Scenes") }, - text = { Text("Save unsaved scenes?") }, + title = { Text(DR.strings.unsaved_scenes_dialog_title.get()) }, + text = { Text(DR.strings.unsaved_scenes_dialog_message.get()) }, onDismissRequest = { /* Noop */ }, buttons = { Column( modifier = Modifier.fillMaxWidth(), ) { Button(onClick = { dismissDialog(ConfirmCloseResult.SaveAll, closeType) }) { - Text("Save and close") + Text(DR.strings.unsaved_scenes_dialog_positive_button.get()) } Button(onClick = { dismissDialog( @@ -171,10 +172,10 @@ internal fun confirmCloseDialog( ApplicationState.CloseType.None ) }) { - Text("Cancel") + Text(DR.strings.unsaved_scenes_dialog_neutral_button.get()) } Button(onClick = { dismissDialog(ConfirmCloseResult.Discard, closeType) }) { - Text("Discard and close") + Text(DR.strings.unsaved_scenes_dialog_negative_button.get()) } } }, diff --git a/desktop/src/jvmMain/kotlin/com/darkrockstudios/apps/hammer/desktop/ProjectEditorWindow.kt b/desktop/src/jvmMain/kotlin/com/darkrockstudios/apps/hammer/desktop/ProjectEditorWindow.kt index 88b1b0f77..6f5f18ba1 100644 --- a/desktop/src/jvmMain/kotlin/com/darkrockstudios/apps/hammer/desktop/ProjectEditorWindow.kt +++ b/desktop/src/jvmMain/kotlin/com/darkrockstudios/apps/hammer/desktop/ProjectEditorWindow.kt @@ -22,6 +22,7 @@ import com.darkrockstudios.apps.hammer.common.AppCloseManager import com.darkrockstudios.apps.hammer.common.components.projectroot.ProjectRoot import com.darkrockstudios.apps.hammer.common.components.projectroot.ProjectRootComponent import com.darkrockstudios.apps.hammer.common.compose.Ui +import com.darkrockstudios.apps.hammer.common.compose.moko.get import com.darkrockstudios.apps.hammer.common.compose.rememberMainDispatcher import com.darkrockstudios.apps.hammer.common.data.ProjectDef import com.darkrockstudios.apps.hammer.common.projectroot.ProjectRootUi @@ -58,7 +59,7 @@ internal fun ApplicationScope.ProjectEditorWindow( LifecycleController(lifecycle, windowState) Window( - title = "Hammer - ${projectDef.name}", + title = DR.strings.project_window_title.get(projectDef.name), state = windowState, icon = painterResource("icon.png"), onCloseRequest = { onRequestClose(component, app, ApplicationState.CloseType.Application) } @@ -101,11 +102,11 @@ private fun FrameWindowScope.EditorMenuBar( val menu by app.menu.subscribeAsState() MenuBar { - Menu("File") { - Item("Close Project", onClick = { + Menu(DR.strings.project_window_menu_file.get()) { + Item(DR.strings.project_window_menu_item_close.get(), onClick = { onRequestClose(component, app, ApplicationState.CloseType.Project) }) - Item("Exit", onClick = { + Item(DR.strings.project_window_menu_item_exit.get(), onClick = { onRequestClose(component, app, ApplicationState.CloseType.Application) }) } @@ -133,8 +134,8 @@ private fun AppContent(component: ProjectRoot) { NavigationRail(modifier = Modifier.padding(top = Ui.Padding.M)) { destinations.forEach { item -> NavigationRailItem( - icon = { Icon(imageVector = getDestinationIcon(item), contentDescription = item.text) }, - label = { Text(item.text) }, + icon = { Icon(imageVector = getDestinationIcon(item), contentDescription = item.text.get()) }, + label = { Text(item.text.get()) }, selected = router.active.instance.getLocationType() == item, onClick = { component.showDestination(item) } ) diff --git a/desktop/src/jvmMain/kotlin/com/darkrockstudios/apps/hammer/desktop/ProjectSelectionWindow.kt b/desktop/src/jvmMain/kotlin/com/darkrockstudios/apps/hammer/desktop/ProjectSelectionWindow.kt index 110ed8b1b..c740af48e 100644 --- a/desktop/src/jvmMain/kotlin/com/darkrockstudios/apps/hammer/desktop/ProjectSelectionWindow.kt +++ b/desktop/src/jvmMain/kotlin/com/darkrockstudios/apps/hammer/desktop/ProjectSelectionWindow.kt @@ -26,6 +26,7 @@ import com.darkrockstudios.apps.hammer.base.BuildMetadata import com.darkrockstudios.apps.hammer.common.components.projectselection.ProjectSelection import com.darkrockstudios.apps.hammer.common.components.projectselection.ProjectSelectionComponent import com.darkrockstudios.apps.hammer.common.compose.Ui +import com.darkrockstudios.apps.hammer.common.compose.moko.get import com.darkrockstudios.apps.hammer.common.data.ProjectDef import com.darkrockstudios.apps.hammer.common.projectselection.ProjectSelectionUi import com.darkrockstudios.apps.hammer.common.projectselection.getLocationIcon @@ -50,7 +51,7 @@ internal fun ApplicationScope.ProjectSelectionWindow( LifecycleController(lifecycle, windowState) Window( - title = "Project Selection", + title = DR.strings.account_window_title.get(), state = windowState, onCloseRequest = ::exitApplication, icon = painterResource("icon.png"), @@ -61,8 +62,8 @@ internal fun ApplicationScope.ProjectSelectionWindow( NavigationRail(modifier = Modifier.padding(top = Ui.Padding.M)) { ProjectSelection.Locations.values().forEach { item -> NavigationRailItem( - icon = { Icon(imageVector = getLocationIcon(item), contentDescription = item.text) }, - label = { Text(item.text) }, + icon = { Icon(imageVector = getLocationIcon(item), contentDescription = item.text.get()) }, + label = { Text(item.text.get()) }, selected = item == slot.child?.configuration?.location, onClick = { component.showLocation(item) } ) diff --git a/gradle.properties b/gradle.properties index b6527df57..b2f8c196e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,9 +1,9 @@ org.gradle.jvmargs=-Xmx4096M org.gradle.configureondemand=false # Release versions -app_version=1.0.7 +app_version=1.0.8 data_version=1.0 -android_version_code=5 +android_version_code=6 kotlin.code.style=official kotlin.native.binary.memoryModel=experimental kotlin.native.binary.freezing=disabled @@ -30,7 +30,7 @@ kotlinx_serialization_version=1.5.0 mockk_version=1.13.5 # This should match what ever decompose is referncing internally essenty_version=1.1.0 -moko_resources_version=0.21.2 +moko_resources_version=0.23.0 ktor_version=2.3.0 sqldelight_version=2.0.0-alpha05 #ktorfit_version=1.0.0