Compare commits

..

67 commits

Author SHA1 Message Date
Adam Brown
cdfcd57f9e Merge branch 'develop' into release 2026-09-24 23:49:33 -07:00
Wavesonics
61a8d5bd47
Merge branch 'develop' into release 2026-09-23 07:56:26 -07:00
Adam Brown
533e487aa9 Merge branch 'develop' into release 2026-09-20 10:17:54 -07:00
Wavesonics
c4efb98100
Merge branch 'develop' into release 2026-09-12 00:54:52 -07:00
Wavesonics
d6d3bc9131 Merge branch 'develop' into release 2026-09-04 20:10:31 -07:00
Wavesonics
fe4e147b91 Merge branch 'develop' into release 2026-09-03 20:27:43 -07:00
Wavesonics
e5acd83d8d
Merge branch 'develop' into release 2026-09-01 20:52:35 -07:00
Adam Brown
19d5f73627 Merge branch 'develop' into release 2026-08-22 17:50:26 -07:00
Adam Brown
051efe8229 Merge branch 'develop' into release 2026-08-22 10:02:44 -07:00
Adam Brown
8371ac829a Merge branch 'develop' into release 2026-08-21 23:53:07 -07:00
Adam Brown
aa49a31c72 Merge branch 'develop' into release 2026-08-18 00:03:58 -07:00
Adam Brown
2c78e42393 Merge branch 'develop' into release 2026-08-16 22:40:12 -07:00
Adam Brown
be4f32d859 Merge branch 'develop' into release 2026-08-11 01:07:15 -07:00
Adam Brown
0b56612f8e Merge branch 'develop' into release 2026-08-08 14:37:08 -07:00
Adam Brown
545cebd996 Merge branch 'develop' into release 2026-08-04 21:50:37 -07:00
Adam Brown
c8f7d92bf3 Merge branch 'develop' into release 2026-07-27 13:47:33 -07:00
Adam Brown
0829dc1bd2 Merge branch 'develop' into release 2026-07-23 14:37:59 -07:00
Adam Brown
a432599c69 Merge branch 'develop' into release 2026-07-21 09:11:07 -07:00
Adam Brown
8c04d9f28a Merge branch 'develop' into release 2026-07-12 23:32:19 -07:00
Adam Brown
5c18153ff0 Merge branch 'develop' into release 2026-07-07 23:24:53 -07:00
Adam Brown
3168bc4527 Merge branch 'develop' into release 2026-06-30 15:18:21 -07:00
Adam Brown
d397836c20 Merge branch 'develop' into release 2026-06-29 22:17:57 -07:00
Adam Brown
6af84d4ca2 Merge branch 'develop' into release 2026-06-27 23:53:36 -07:00
Adam Brown
a926e71783 Merge branch 'develop' into release 2026-06-27 00:39:54 -07:00
Adam Brown
58aa8c9f87 Merge branch 'develop' into release 2026-06-23 10:11:43 -07:00
Adam Brown
e7862f8a61 Merge branch 'develop' into release 2026-06-23 00:46:52 -07:00
Adam Brown
85a733221c Merge branch 'develop' into release 2026-06-22 22:04:08 -07:00
Adam Brown
a6a94d5517 Merge branch 'develop' into release 2026-06-16 00:36:17 -07:00
Adam Brown
cd5d016820 Merge branch 'develop' into release 2026-06-13 12:45:56 -07:00
Adam Brown
4fe5344ce0
Merge branch 'develop' into release 2026-06-10 19:42:58 -07:00
Adam Brown
1047a65365
Merge branch 'develop' into release 2026-06-08 00:10:12 -07:00
Wavesonics
82723313c7 Merge branch 'develop' into release 2026-06-04 21:57:18 -07:00
Adam Brown
11a6f6307a
Merge branch 'develop' into release 2026-06-03 00:39:46 -07:00
Adam Brown
6cbaf3357b
Merge branch 'develop' into release 2026-06-02 00:30:16 -07:00
Adam Brown
9e66f60136
Merge branch 'develop' into release 2026-06-01 23:11:52 -07:00
Adam Brown
babf1a74ac
Merge branch 'develop' into release 2026-05-17 01:27:51 -07:00
Adam Brown
5281b2a6f0
Merge branch 'develop' into release 2026-05-15 00:31:38 -07:00
Adam Brown
492be1c4eb
Merge branch 'develop' into release 2026-05-14 21:56:22 -07:00
Adam Brown
14fbb67a6e
Merge branch 'develop' into release 2026-05-14 01:47:02 -07:00
Adam Brown
4310688098
Fix new-line text editor bug 2026-05-04 23:33:55 -07:00
Adam Brown
d3221e8747
Merge branch 'develop' into release 2026-04-26 20:26:59 -07:00
Adam Brown
7ad0244092
Merge branch 'develop' into release 2026-02-07 23:36:13 -08:00
Adam Brown
cd03bea8e0
Merge branch 'develop' into release 2026-02-07 22:30:43 -08:00
Adam Brown
504f2a10a8
Prepared for release: v2.1.4 2026-02-07 22:29:19 -08:00
Adam Brown
69ed22d0dc Merge branch 'develop' into release 2026-01-24 00:20:41 -08:00
Adam Brown
eb1b36394b Merge branch 'develop' into release 2026-01-17 13:23:05 -08:00
Adam Brown
6142df4311 Merge branch 'develop' into release 2026-01-15 23:54:11 -08:00
Adam Brown
a3ee323f7d Merge branch 'develop' into release
# Conflicts:
#	flatpak/com.darkrockstudios.hammer.metainfo.xml
2026-01-15 23:25:25 -08:00
Adam Brown
894aa7d01a Merge branch 'develop' into release
# Conflicts:
#	flatpak/com.darkrockstudios.hammer.metainfo.xml
2026-01-15 23:16:56 -08:00
Adam Brown
754d43befd Merge branch 'develop' into release 2026-01-13 23:56:36 -08:00
Adam Brown
99cfd47d44 Prepared for release: v2.1.0 2026-01-13 23:55:07 -08:00
Adam Brown
81f4c46f77 Merge branch 'develop' into release 2026-01-11 02:04:23 -08:00
Adam Brown
807df5f607 Merge branch 'develop' into release 2026-01-10 22:16:44 -08:00
Adam Brown
0705a11a45 Merge branch 'develop' into release 2026-01-06 18:39:43 -08:00
Adam Brown
84e5325858 Merge branch 'develop' into release 2026-01-06 00:47:37 -08:00
Adam Brown
b0a0b81b70 Merge branch 'develop' into release 2026-01-05 00:42:00 -08:00
Adam Brown
dddf55f32b Merge branch 'develop' into release 2026-01-03 01:43:56 -08:00
Adam Brown
f3405c2c51 Merge branch 'develop' into release 2025-12-31 16:23:12 -08:00
Adam Brown
b66b7f07f1 Merge branch 'develop' into release 2025-12-30 00:22:00 -08:00
Adam Brown
72110a3c3c Merge branch 'develop' into release 2025-12-29 13:52:28 -08:00
Adam Brown
840c2619e8 Merge branch 'develop' into release 2025-12-27 01:30:52 -08:00
Adam Brown
502d6f4173 Merge branch 'develop' into release 2025-12-26 23:35:36 -08:00
Adam Brown
dc7f071186 Merge branch 'develop' into release 2025-12-26 13:55:43 -08:00
Adam Brown
2cfaadbbf7 Merge branch 'develop' into release 2025-12-25 12:23:40 -08:00
Adam Brown
80a3dbe0bb Merge branch 'develop' into release 2025-12-24 23:16:50 -08:00
Adam Brown
d623a5ace6 Merge branch 'develop' into release 2025-12-21 02:16:03 -08:00
Adam Brown
202552b68b Prepared for release: v1.11.1 2025-12-21 02:03:25 -08:00
351 changed files with 1464 additions and 11913 deletions

View file

@ -72,7 +72,7 @@ jobs:
timeout_minutes: 30
max_attempts: 3
shell: pwsh
command: ./gradlew :desktop:packageReleaseMsi -Pchannel=github -Pformat=msi
command: ./gradlew :desktop:packageReleaseMsi -Pchannel=github
- name: Rename MSI Artifact
shell: cmd
@ -104,7 +104,7 @@ jobs:
timeout_minutes: 30
max_attempts: 3
shell: pwsh
command: ./gradlew :desktop:packageReleaseExe -Pchannel=github -Pformat=exe
command: ./gradlew :desktop:packageReleaseExe -Pchannel=github
- name: Rename EXE Artifact
shell: cmd
@ -130,17 +130,6 @@ jobs:
echo Successfully renamed file to "hammer.exe"
- name: Write update digests
shell: pwsh
run: ./gradlew writeAssetDigests "-PassetFiles=desktop/build/installers/main-release/msi/hammer.msi,desktop/build/installers/main-release/exe/hammer.exe"
- name: Upload update digests
uses: actions/upload-artifact@v7
with:
name: update-digests-windows
path: desktop/build/installers/main-release/*/hammer.*.sha512
if-no-files-found: error
- name: Create Release
uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0
env:
@ -203,7 +192,7 @@ jobs:
with:
timeout_minutes: 30
max_attempts: 3
command: ./gradlew :desktop:packageReleaseDeb -Pchannel=github -Pformat=deb
command: ./gradlew :desktop:packageReleaseDeb -Pchannel=github
- name: Rename DEB Artifact
run: |
@ -227,7 +216,7 @@ jobs:
with:
timeout_minutes: 30
max_attempts: 3
command: ./gradlew :desktop:packageReleaseRpm -Pchannel=github -Pformat=rpm
command: ./gradlew :desktop:packageReleaseRpm -Pchannel=github
- name: Rename RPM Artifact
run: |
@ -251,17 +240,7 @@ jobs:
with:
timeout_minutes: 30
max_attempts: 3
command: ./gradlew buildDistAppImage -Pchannel=appimage -Pformat=appimage
- name: Write update digests
run: ./gradlew writeAssetDigests "-PassetFiles=desktop/build/installers/main-release/deb/hammer.deb,desktop/build/installers/main-release/rpm/hammer.rpm,desktop/build/installers/main-release/appimage/hammer.AppImage"
- name: Upload update digests
uses: actions/upload-artifact@v7
with:
name: update-digests-linux
path: desktop/build/installers/main-release/*/hammer.*.sha512
if-no-files-found: error
command: ./gradlew buildDistAppImage -Pchannel=appimage
- name: Create Release
uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0
@ -304,7 +283,7 @@ jobs:
with:
timeout_minutes: 30
max_attempts: 3
command: ./gradlew :desktop:packageReleaseDmg -Pchannel=github -Pformat=dmg
command: ./gradlew :desktop:packageReleaseDmg -Pchannel=github
- name: Rename DMG Artifact
run: |
@ -328,7 +307,7 @@ jobs:
with:
timeout_minutes: 30
max_attempts: 3
command: ./gradlew :desktop:packageReleasePkg -Pchannel=github -Pformat=pkg
command: ./gradlew :desktop:packageReleasePkg -Pchannel=github
- name: Rename PKG Artifact
run: |
@ -349,16 +328,6 @@ jobs:
echo "Contents of pkg directory:"
ls -la
- name: Write update digests
run: ./gradlew writeAssetDigests "-PassetFiles=desktop/build/installers/main-release/dmg/hammer.dmg,desktop/build/installers/main-release/pkg/hammer.pkg"
- name: Upload update digests
uses: actions/upload-artifact@v7
with:
name: update-digests-macos
path: desktop/build/installers/main-release/*/hammer.*.sha512
if-no-files-found: error
- name: Create Release
uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0
env:
@ -496,9 +465,6 @@ jobs:
echo "File renamed successfully."
- name: Rename R8 Mapping
run: cp "$GITHUB_WORKSPACE/android/build/outputs/mapping/release/mapping.txt" "$GITHUB_WORKSPACE/android/build/outputs/apk/release/hammer-apk-mapping.txt"
- name: Create Release
uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0
env:
@ -508,7 +474,7 @@ jobs:
prerelease: true
artifactErrorsFailBuild: true
allowUpdates: true
artifacts: "${{ github.workspace }}/android/build/outputs/apk/release/hammer.apk,${{ github.workspace }}/android/build/outputs/apk/release/hammer-apk-mapping.txt"
artifacts: "${{ github.workspace }}/android/build/outputs/apk/release/hammer.apk"
snap:
needs: tests
@ -625,47 +591,6 @@ jobs:
- name: Fetch tags
run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*
- name: Set up JDK
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '21'
- name: Gradle caching
uses: actions/cache@v6
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
# The desktop updater reads latest*.yml from the release. Each platform job hashed
# the installer it attached and handed the digest over as a workflow artifact, so
# nothing is downloaded back and no release lookup is needed. Attached before the
# draft flips, so a published release never exists without them.
- name: Collect update digests
uses: actions/download-artifact@v7
with:
pattern: update-digests-*
merge-multiple: true
path: ${{ runner.temp }}/update-digests
- name: Write update metadata
run: ./gradlew writeUpdateMetadata "-PreleaseDigestsDir=${{ runner.temp }}/update-digests" --no-daemon
- name: Attach update metadata
uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
draft: true
prerelease: true
artifactErrorsFailBuild: true
allowUpdates: true
artifacts: "${{ github.workspace }}/build/update-metadata/latest.yml,${{ github.workspace }}/build/update-metadata/latest-mac.yml,${{ github.workspace }}/build/update-metadata/latest-linux.yml"
- name: Get last tag message
run: |
TAG_NAME=${GITHUB_REF#refs/tags/}

View file

@ -89,15 +89,3 @@ jobs:
esac
echo "Running fastlane $LANE for track=$TRACK"
bundle exec fastlane "$LANE"
# Keeps the mappings for the exact Play builds next to the release, so prod
# crash traces can be retraced without the Play Console.
- name: Attach R8 mappings to the GitHub release
if: github.event_name == 'release'
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ github.event.release.tag_name }}
run: |
cp android/build/outputs/mapping/release/mapping.txt hammer-google-play-mapping.txt
cp wear/build/outputs/mapping/release/mapping.txt hammer-wear-mapping.txt
gh release upload "$TAG" hammer-google-play-mapping.txt hammer-wear-mapping.txt --clobber

View file

@ -27,7 +27,7 @@ jobs:
cache: gradle
- name: Build MSIX package
run: ./gradlew :desktop:packageMsix -Pchannel=ms-store -Pformat=msix
run: ./gradlew :desktop:packageMsix -Pchannel=ms-store
- name: Upload Gradle problems report on failure
if: failure()

View file

@ -11,8 +11,6 @@ jobs:
publish-google-play:
if: ${{ !contains(github.event.release.tag_name, '+') || contains(github.event.release.tag_name, '+google-play') }}
uses: ./.github/workflows/publish-google-play.yml
permissions:
contents: write
secrets: inherit
with:
track: production

View file

@ -1,39 +1,5 @@
# Changelog
## [3.12.0] - 2026-10-5
New features
- Reader Mode: read your story as prose, one chapter at a time, instead of through the scene editor. Open it from the Read button under the scene list, the book icon in the scene editor, or the editor menu. The scene list becomes a table of contents, and the reader has its own font size
- Export: three new options in the Export Story dialog. Number chapter titles, pick a font (Word, RTF and EPUB), and choose whether to keep blank lines between paragraphs
- Android and iOS: an OLED theme, the dark theme on a pure black background
- Desktop (Windows and Linux installs from GitHub): Hammer tells you when a newer release is out and can update itself from the project list or the About screen. macOS DMG installs are told and sent to the release page. Automatic checks can be turned off in Settings
- Text editor 3.0: Huge improvements in input and performance
- Text editor: change the text size with Ctrl/Cmd and Plus, Minus or 0, or from the format bar's overflow menu
- Text editor: Delete in the right-click menu
- Turkish translation
- Web: a fifth reader reaction on public stories, "Surprised me"
Improvements
- Syncing with unsaved work now asks Save, Discard or Cancel first, where before it silently saved every unsaved scene
- Open notes, timeline events, encyclopedia entries and the scene title, outline and notes refresh when a sync or conflict resolution changes them. An edit in progress keeps its draft
- The Backup Manager lists backups of projects that are no longer on disk, so a deleted project can be restored
- Exports no longer number chapter titles unless you turn the new option on. Word, RTF, PDF and Markdown used to number them always
- Text editor: the heading button writes real Markdown headings, and Hammer's shortcuts land on the same keys on non-QWERTY layouts
- Android: a launch splash screen on every supported version
- Translations and dependencies updated
Fixes
- Android: unsaved scene edits could be lost when the app was closed in the background. They now come back as unsaved scenes the next time the project opens [#983]
- Export: unsaved scene edits were left out of the exported file [#983]
- Restoring a backup from the Backup Manager made the project disappear from the project list
- Choosing Discard on close with automatic sync on still saved and uploaded the discarded scenes
- PDF export: long paragraphs ran off the bottom of the page or left large blank gaps
- Android: a crash on launch when the saved login could not be decrypted, for example after restoring the device from a backup. You are asked to sign in again
- iOS: a crash when tapping into a text editor
- Server URL, number, tag and word-list fields no longer autocorrect or capitalize, which put a space after each "." in a server URL
- Encyclopedia references no longer match a name inside a longer word with accented or non-Latin letters ("Ana" in "Anaïs")
## [3.11.0] - 2026-9-24
New features

View file

@ -174,19 +174,6 @@ enum on purpose: every channel-conditional branch is a greppable `when` on the t
Capability flags (`canSelfUpdate`, `isSandboxed`, …) can be introduced later, from real
duplication rather than a guess at the axes.
The build side reads the same token through `Project.distributionChannel()` in buildSrc:
`desktop/build.gradle.kts` derives its Mac App Store packaging mode (`isAppStoreRelease`:
sandbox entitlements, provisioning profiles, signing) from `-Pchannel=mac-app-store`. Together
with `-PbuildNumber=N`, that is everything `build-appstore.sh` passes.
Packaging runs carry a second stamp, `-Pformat=<token>` (`msi`, `exe`, `deb`, `rpm`, `dmg`,
`pkg`, `appimage`, `msix`, `snap`, `flatpak`), read the same way into
`BuildMetadata.PACKAGE_FORMAT` and `PackageFormat.current`. The channel says which vehicle a
build is for; the format says which installer it came out of, which is what the desktop
updater needs to pick its download. Omitting it gives `none`, an unknown token fails the
build, and each release step passes its own. Two packaging tasks in one Gradle invocation
would both carry the first task's format, so the release workflow runs one per step.
Note that every branch compiles into every build, so a store-forbidden code path is
physically present in a store binary even when unreachable. If a store ever objects to
presence rather than behaviour, promote that one case to a real source set rather than

View file

@ -40,7 +40,6 @@ dependencies {
implementation(libs.work.runtime.ktx)
implementation(libs.material)
implementation(libs.appcompat)
implementation(libs.core.splashscreen)
implementation(libs.multiplatform.settings)
androidTestImplementation(libs.androidx.junit)

View file

@ -69,7 +69,7 @@ class WearPairingActivity : ComponentActivity(), KoinComponent {
setContent {
val isDark = when (settings.uiTheme) {
UiTheme.Light -> false
UiTheme.Dark, UiTheme.OledBlack -> true
UiTheme.Dark -> true
UiTheme.FollowSystem -> isSystemInDarkTheme()
}

View file

@ -23,7 +23,6 @@
<activity
android:name=".ProjectSelectActivity"
android:theme="@style/Theme.Hammer.Starting"
android:exported="true"
android:windowSoftInputMode="adjustResize">
<intent-filter>

View file

@ -109,7 +109,7 @@ class ProjectRootActivity : AppCompatActivity() {
val settingsState by globalSettings.subscribeAsState()
val isDark = when (settingsState.uiTheme) {
UiTheme.Light -> false
UiTheme.Dark, UiTheme.OledBlack -> true
UiTheme.Dark -> true
UiTheme.FollowSystem -> isSystemInDarkTheme()
}
@ -220,8 +220,7 @@ class ProjectRootActivity : AppCompatActivity() {
// Only intercept back when at Home AND there's potential unsaved work to confirm
// Otherwise, let Android's default back behavior close the activity
val unsavedBuffers by component.unsavedBuffers.subscribeAsState()
BackHandler(enabled = backEnabled && (unsavedBuffers || shouldConfirmClose.isNotEmpty())) {
BackHandler(enabled = backEnabled && (component.hasUnsavedBuffers() || shouldConfirmClose.isNotEmpty())) {
component.requestClose()
}

View file

@ -14,7 +14,6 @@ import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.runtime.ExperimentalComposeApi
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalContext
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.lifecycleScope
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.retainedComponent
@ -44,7 +43,6 @@ class ProjectSelectActivity : AppCompatActivity() {
private var settingsUpdateJob: Job? = null
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
super.onCreate(savedInstanceState)
enableEdgeToEdge()
@ -63,7 +61,7 @@ class ProjectSelectActivity : AppCompatActivity() {
val settingsState by globalSettings.subscribeAsState()
val isDark = when (settingsState.uiTheme) {
UiTheme.Light -> false
UiTheme.Dark, UiTheme.OledBlack -> true
UiTheme.Dark -> true
UiTheme.FollowSystem -> isSystemInDarkTheme()
}

View file

@ -18,7 +18,6 @@ import com.darkrockstudios.apps.hammer.common.components.projectselection.projec
import com.darkrockstudios.apps.hammer.common.components.storyeditor.metadata.ProjectMetadata
import com.darkrockstudios.apps.hammer.base.http.storyideas.StoryIdea
import com.darkrockstudios.apps.hammer.common.data.ImportOptions
import com.darkrockstudios.apps.hammer.common.data.appupdate.AppUpdateState
import com.darkrockstudios.apps.hammer.common.data.Msg
import com.darkrockstudios.apps.hammer.common.data.ProjectDef
import com.darkrockstudios.apps.hammer.common.fileio.HPath
@ -96,8 +95,6 @@ val component = object : ProjectSelection {
override val changelog: Value<ProjectSelection.ChangelogState> =
MutableValue(ProjectSelection.ChangelogState())
override val appUpdate: Value<AppUpdateState> = MutableValue(AppUpdateState.Unsupported)
override fun isAtRoot() = false
override fun onBack() {}
override fun showLocation(location: ProjectSelection.Locations) {}
@ -105,8 +102,6 @@ val component = object : ProjectSelection {
override fun openLatestRelease() {}
override fun dismissChangelog() {}
override fun showChangelog() {}
override fun updateApp() {}
override fun dismissUpdate() {}
override val backHandler = dummyBackHandler
}

View file

@ -97,7 +97,7 @@ class AddNoteActivity : ComponentActivity(), KoinComponent {
val settingsState by globalSettings.subscribeAsState()
val isDark = when (settingsState.uiTheme) {
UiTheme.Light -> false
UiTheme.Dark, UiTheme.OledBlack -> true
UiTheme.Dark -> true
UiTheme.FollowSystem -> isSystemInDarkTheme()
}

View file

@ -90,7 +90,7 @@ class AddNoteWidgetConfigActivity : ComponentActivity() {
val settingsState by globalSettings.subscribeAsState()
val isDark = when (settingsState.uiTheme) {
UiTheme.Light -> false
UiTheme.Dark, UiTheme.OledBlack -> true
UiTheme.Dark -> true
UiTheme.FollowSystem -> isSystemInDarkTheme()
}

View file

@ -88,7 +88,7 @@ class StoryInfoWidgetConfigActivity : ComponentActivity() {
val settingsState by globalSettings.subscribeAsState()
val isDark = when (settingsState.uiTheme) {
UiTheme.Light -> false
UiTheme.Dark, UiTheme.OledBlack -> true
UiTheme.Dark -> true
UiTheme.FollowSystem -> isSystemInDarkTheme()
}

View file

@ -5,8 +5,8 @@
<string name="wear_pairing_body">The watch will sync as %1$s with a session of its own. You can sign it out from the watch at any time.</string>
<string name="wear_pairing_not_signed_in">Sign in to a sync server on this phone first, then pair the watch again.</string>
<string name="wear_pairing_approve">Pair</string>
<string name="wear_pairing_decline">Decline</string>
<string name="wear_pairing_close">Close</string>
<string name="wear_pairing_decline">Refuser</string>
<string name="wear_pairing_close">Fermer</string>
<string name="wear_pairing_toast_success">Watch paired</string>
<string name="wear_pairing_toast_failure">The watch could not be paired</string>
<string name="wear_pairing_channel_name">Watch pairing</string>

View file

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="wear_pairing_masthead">APPAIRER LA MONTRE</string>
<string name="wear_pairing_title">Se connecter à %1$s sur Hammer ?</string>
<string name="wear_pairing_body">La montre sera synchronisée en tant que %1$s avec sa propre session. Vous pouvez la déconnecter depuis la montre à tout moment.</string>
<string name="wear_pairing_not_signed_in">Connectez-vous au serveur sur ce smartphone en premier, puis appairez la montre de nouveau.</string>
<string name="wear_pairing_approve">Appairer</string>
<string name="wear_pairing_decline">Refuser</string>
<string name="wear_pairing_close">Fermer</string>
<string name="wear_pairing_toast_success">Montre appairée</string>
<string name="wear_pairing_toast_failure">La montre n\'a pas pu être appairée</string>
<string name="wear_pairing_channel_name">Appairage de la montre</string>
<string name="wear_pairing_notification_title">Appairez votre montre</string>
<string name="wear_pairing_notification_text">Tapoter pour que votre montre se synchronise avec votre compte Hammer</string>
</resources>

View file

@ -46,3 +46,5 @@
<string name="story_info_widget_strip_words_unit">P</string>
<string name="story_info_widget_continue_label">CONTINUA ·</string>
</resources>

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="wear_pairing_masthead">PAIR WATCH</string>
<string name="wear_pairing_title">Sign in %1$s to Hammer?</string>
<string name="wear_pairing_body">The watch will sync as %1$s with a session of its own. You can sign it out from the watch at any time.</string>
<string name="wear_pairing_not_signed_in">Sign in to a sync server on this phone first, then pair the watch again.</string>
<string name="wear_pairing_approve">Pair</string>
<string name="wear_pairing_decline">Rifiuta</string>
<string name="wear_pairing_close">Chiudi</string>
<string name="wear_pairing_toast_success">Watch paired</string>
<string name="wear_pairing_toast_failure">The watch could not be paired</string>
<string name="wear_pairing_channel_name">Watch pairing</string>
<string name="wear_pairing_notification_title">Pair your watch</string>
<string name="wear_pairing_notification_text">Tap to let your watch sync with your Hammer account</string>
</resources>

View file

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="wear_pairing_masthead">ASSOCIA OROLOGIO</string>
<string name="wear_pairing_title">Accedere come %1$s su Hammer?</string>
<string name="wear_pairing_body">L’orologio si sincronizzerà come %1$s con una propria sessione. Puoi disconnetterlo dall’orologio in qualsiasi momento.</string>
<string name="wear_pairing_not_signed_in">Accedi prima a un server di sincronizzazione su questo telefono, poi associa nuovamente l’orologio.</string>
<string name="wear_pairing_approve">Associa</string>
<string name="wear_pairing_decline">Rifiuta</string>
<string name="wear_pairing_close">Chiudi</string>
<string name="wear_pairing_toast_success">Orologio associato</string>
<string name="wear_pairing_toast_failure">Impossibile associare l’orologio</string>
<string name="wear_pairing_channel_name">Associazione orologio</string>
<string name="wear_pairing_notification_title">Associa il tuo orologio</string>
<string name="wear_pairing_notification_text">Tocca per consentire all’orologio di sincronizzarsi con il tuo account Hammer</string>
</resources>

View file

@ -1,48 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Hammer</string>
<string name="title_activity_add_note">Not Ekle</string>
<string name="loading">Yükleniyor…</string>
<string name="title_activity_configure_note_widget">Widget\'ı Yapılandır</string>
<string name="overflow_menu_button">Diğer Menüler</string>
<string name="navdrawer_button">Gezinti Çekmecesi</string>
<string name="note_widget_dialog_title">Notu Şuraya Ekle:</string>
<string name="note_widget_dialog_save_button">Kaydet</string>
<string name="note_widget_dialog_cancel_button">İptal</string>
<string name="note_widget_dialog_projects_dropdown">Projeler</string>
<string name="note_widget_dialog_failure_bad_project">Proje bulunamadı: %1$s</string>
<string name="note_widget_confirm_cancel_title">Nottan Vazgeçilsin mi?</string>
<string name="note_widget_confirm_cancel_positive">Vazgeç</string>
<string name="note_widget_confirm_cancel_negative">İptal</string>
<string name="note_widget_button_description">Not Ekle</string>
<string name="note_widget_toast_no_projects">Proje yok</string>
<string name="note_widget_toast_success">Not Eklendi</string>
<string name="action_add_note_short_label">Not Ekle</string>
<string name="action_add_note_long_label">Hikâye Notu Ekle</string>
<string name="action_add_note_disabled">Not ekleme şu anda devre dışı</string>
<string name="title_activity_add_note_widget_config">Widget\'ı Yapılandır</string>
<string name="note_widget_config_title">Not Widget\'ını yapılandır:</string>
<string name="note_widget_config_project_checkbox">Notları belirtilen projeye ekle</string>
<string name="note_widget_config_project_checkbox_explained">Eğer seçili bir proje yoksa her seferinde bir proje seçmen istenir.</string>
<string name="stories_list_widget_description">Hammer · Kütüphane — bir hikâyeyi aç</string>
<string name="story_info_widget_description">Hammer · Hikâye — bir proje için özet istatistikler</string>
<string name="story_info_widget_config_title">Hikâye Bilgisi Widget\'ı:</string>
<string name="title_activity_story_info_widget_config">Hikâye Bilgisi Widget\'ını Yapılandır</string>
<string name="story_info_widget_unconfigured">Yapılandırmak için dokun</string>
<string name="story_info_widget_folio_header">§ %1$s · HİKÂYE</string>
<string name="story_info_widget_byline">- %1$s</string>
<string name="story_info_widget_words_label">WORDS</string>
<string name="story_info_widget_goal_summary">%1$d%% OF %2$s %3$s</string>
<string name="story_info_widget_cadence_today">TODAY</string>
<string name="story_info_widget_cadence_this_week">THIS WEEK</string>
<string name="story_info_widget_today_words">+%1$d TODAY</string>
<string name="story_info_widget_no_session_today">NO SESSION TODAY</string>
<string name="story_info_widget_streak">%1$dD STREAK</string>
<string name="story_info_widget_sessions_header">SESSIONS · 7D</string>
<string name="story_info_widget_week_total">%1$s W</string>
<string name="story_info_widget_action_open">OPEN</string>
<string name="story_info_widget_action_note">NOTE</string>
<string name="story_info_widget_strip_folio">§ %1$s</string>
<string name="story_info_widget_strip_words_unit">W</string>
<string name="story_info_widget_continue_label">CONTINUE ·</string>
</resources>

View file

@ -2,10 +2,4 @@
<resources>
<style name="Theme.Hammer" parent="Theme.AppCompat.DayNight.NoActionBar">
</style>
<style name="Theme.Hammer.Starting" parent="Theme.SplashScreen">
<item name="windowSplashScreenBackground">@color/ic_launcher_background</item>
<item name="windowSplashScreenAnimatedIcon">@mipmap/ic_launcher_foreground</item>
<item name="postSplashScreenTheme">@style/Theme.Hammer</item>
</style>
</resources>
</resources>

View file

@ -3,5 +3,4 @@
<exclude domain="file" path="HammerProjects/server.json"/>
<exclude domain="file" path="HammerProjects/sync.json"/>
<exclude domain="file" path="HammerProjects/.backups"/>
<exclude domain="sharedpref" path="hammer_auth_tokens.xml"/>
</full-backup-content>

View file

@ -4,12 +4,10 @@
<exclude domain="file" path="HammerProjects/server.json"/>
<exclude domain="file" path="HammerProjects/sync.json"/>
<exclude domain="file" path="HammerProjects/.backups"/>
<exclude domain="sharedpref" path="hammer_auth_tokens.xml"/>
</cloud-backup>
<device-transfer>
<exclude domain="file" path="HammerProjects/server.json"/>
<exclude domain="file" path="HammerProjects/sync.json"/>
<exclude domain="file" path="HammerProjects/.backups"/>
<exclude domain="sharedpref" path="hammer_auth_tokens.xml"/>
</device-transfer>
</data-extraction-rules>

View file

@ -1,5 +1,4 @@
import com.darkrockstudios.build.distributionChannel
import com.darkrockstudios.build.packageFormat
import com.darkrockstudios.build.resolveDistributionChannel
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
@ -79,8 +78,12 @@ buildConfig {
buildConfigField("String", "APP_VERSION", "\"${libs.versions.app.get()}\"")
buildConfigField("String", "DATA_VERSION", "\"${libs.versions.data.version.get()}\"")
buildConfigField("String", "CHANNEL", "\"${project.distributionChannel().token}\"")
buildConfigField("String", "PACKAGE_FORMAT", "\"${project.packageFormat().token}\"")
// Mirrors the F-Droid detection in settings.gradle.kts so a plain F-Droid build reports its
// channel without every call site having to pass -Pchannel too.
val isFDroid = project.findProperty("fdroid")?.toString()?.isNotEmpty() == true ||
System.getenv("FDROID_BUILD") != null
val channel = resolveDistributionChannel(project.findProperty("channel")?.toString(), isFDroid)
buildConfigField("String", "CHANNEL", "\"${channel.token}\"")
}
val GIT_TASK_NAME = "install-git-hooks"

View file

@ -37,9 +37,5 @@ enum class DistributionChannel(val token: String, val displayName: String) {
*/
val current: DistributionChannel =
entries.firstOrNull { it.token == BuildMetadata.CHANNEL } ?: DEV
/** The sandboxed, App Review facing desktop build. */
val isMacAppStore: Boolean
get() = current == MAC_APP_STORE
}
}

View file

@ -1,30 +0,0 @@
package com.darkrockstudios.apps.hammer.base
/**
* The installer format this build was packaged as, baked in at build time from `-Pformat=<token>`.
* The second axis next to [DistributionChannel]: the channel says which vehicle the build is for
* and whether it may update itself, the format says which installer artifact it came from. Dev
* runs and anything built without a packaging task report [NONE].
*
* Kept in step with the build-side enum in `buildSrc/src/main/java/packageFormat.kt`.
*/
enum class PackageFormat(val token: String) {
NONE("none"),
MSI("msi"),
EXE("exe"),
DEB("deb"),
RPM("rpm"),
DMG("dmg"),
PKG("pkg"),
APPIMAGE("appimage"),
MSIX("msix"),
SNAP("snap"),
FLATPAK("flatpak"),
;
companion object {
/** An unrecognised token falls back to [NONE] for the same reason [DistributionChannel.current] falls back to DEV. */
val current: PackageFormat =
entries.firstOrNull { it.token == BuildMetadata.PACKAGE_FORMAT } ?: NONE
}
}

View file

@ -32,18 +32,16 @@ object ProseHtml {
/**
* The generating providers [flavour] would use, with prose layout applied. Pass the result to
* [HtmlGenerator]. With [keepBlankLines] off, a lone blank line between paragraphs produces
* nothing and a run of two or more produces a single break.
* [HtmlGenerator].
*/
fun providers(
flavour: MarkdownFlavourDescriptor,
linkMap: LinkMap,
keepBlankLines: Boolean = true,
): Map<IElementType, GeneratingProvider> =
flavour.createHtmlGeneratingProviders(linkMap, null) + mapOf(
MarkdownElementTypes.PARAGRAPH to ProseParagraphProvider,
MarkdownElementTypes.MARKDOWN_FILE to ProseContainerProvider("body", keepBlankLines),
MarkdownElementTypes.BLOCK_QUOTE to ProseContainerProvider("blockquote", keepBlankLines),
MarkdownElementTypes.MARKDOWN_FILE to ProseContainerProvider("body"),
MarkdownElementTypes.BLOCK_QUOTE to ProseContainerProvider("blockquote"),
)
/**
@ -102,10 +100,7 @@ private object ProseParagraphProvider : TrimmingInlineHolderProvider() {
}
/** Emits a break for each blank line the author left between two paragraphs. */
private class ProseContainerProvider(
tag: String,
private val keepBlankLines: Boolean,
) : SimpleTagProvider(tag) {
private class ProseContainerProvider(tag: String) : SimpleTagProvider(tag) {
override fun processNode(
visitor: HtmlGenerator.HtmlGeneratingVisitor,
text: String,
@ -124,12 +119,7 @@ private class ProseContainerProvider(
else -> {
val isParagraph = child.type == MarkdownElementTypes.PARAGRAPH
if (afterParagraph && isParagraph) {
val blanks = (newlines - 1).coerceIn(0, ProseHtml.MAX_CONSECUTIVE_BREAKS)
val breaks = when {
keepBlankLines -> blanks
blanks >= 2 -> 1
else -> 0
}
val breaks = (newlines - 1).coerceIn(0, ProseHtml.MAX_CONSECUTIVE_BREAKS)
repeat(breaks) { visitor.consumeHtml(BREAK) }
}
child.accept(visitor)

View file

@ -1,34 +0,0 @@
package com.darkrockstudios.apps.hammer.base
import kotlin.test.Test
import kotlin.test.assertEquals
class PackageFormatTest {
/**
* Pins the tokens against the build-side enum in `buildSrc/src/main/java/packageFormat.kt`,
* the same way [DistributionChannelTest] pins the channel tokens.
*/
@Test
fun `tokens match the build side enum`() {
val expected = listOf(
"none",
"msi",
"exe",
"deb",
"rpm",
"dmg",
"pkg",
"appimage",
"msix",
"snap",
"flatpak",
)
assertEquals(expected, PackageFormat.entries.map { it.token })
}
@Test
fun `current resolves the baked in token`() {
assertEquals(BuildMetadata.PACKAGE_FORMAT, PackageFormat.current.token)
}
}

View file

@ -7,7 +7,6 @@ import com.darkrockstudios.build.formatStoreNotes
import com.darkrockstudios.build.isPlatformReleaseTag
import com.darkrockstudios.build.registerLinuxDistributionTasks
import com.darkrockstudios.build.registerPublishTasks
import com.darkrockstudios.build.registerUpdateMetadataTasks
import com.darkrockstudios.build.releaseNotesUrl
import com.darkrockstudios.build.storeNotesLength
import com.darkrockstudios.build.updateFlatpakFiles
@ -129,7 +128,6 @@ kover {
registerPublishTasks()
registerLinuxDistributionTasks(libs.versions.app.get())
registerUpdateMetadataTasks(libs.versions.app.get())
// The pre-commit hook runs Gradle, so git's hook env leaks into the daemon and Gradle
// blanks rather than unsets it later; a blank GIT_INDEX_FILE reads as an empty index.

View file

@ -1,7 +1,5 @@
package com.darkrockstudios.build
import org.gradle.api.Project
/**
* Build-side mirror of the runtime `DistributionChannel` enum in `:base`. The two lists have to
* stay in step; `DistributionChannelTest` in `:base` pins the token strings so a change to one
@ -45,13 +43,3 @@ fun resolveDistributionChannel(
DistributionChannel.entries.joinToString(", ") { it.token }
)
}
/**
* The channel this build is for. The F-Droid detection mirrors settings.gradle.kts, which cannot
* see buildSrc, so a plain F-Droid build reports its channel without passing -Pchannel too.
*/
fun Project.distributionChannel(): DistributionChannel {
val isFDroid = findProperty("fdroid")?.toString()?.isNotEmpty() == true ||
System.getenv("FDROID_BUILD") != null
return resolveDistributionChannel(findProperty("channel")?.toString(), isFDroid)
}

View file

@ -1,45 +0,0 @@
package com.darkrockstudios.build
import org.gradle.api.Project
/**
* Build-side mirror of the runtime `PackageFormat` enum in `:base`. The two lists have to stay
* in step; `PackageFormatTest` in `:base` pins the token strings so a change to one without the
* other fails a test rather than shipping a build that reports the wrong format.
*/
enum class PackageFormat(val token: String) {
NONE("none"),
MSI("msi"),
EXE("exe"),
DEB("deb"),
RPM("rpm"),
DMG("dmg"),
PKG("pkg"),
APPIMAGE("appimage"),
MSIX("msix"),
SNAP("snap"),
FLATPAK("flatpak"),
;
companion object {
fun fromToken(token: String): PackageFormat? = entries.firstOrNull { it.token == token }
}
}
/**
* Resolves the installer format for this build from `-Pformat=<token>`, defaulting to [NONE]
* for anything that is not a packaging run. Throws on an unknown token, so a typo in a release
* workflow fails the build instead of quietly shipping an installer that cannot name itself.
*/
fun resolvePackageFormat(formatProperty: String?): PackageFormat {
val requested = formatProperty?.trim()?.takeIf { it.isNotEmpty() } ?: return PackageFormat.NONE
return PackageFormat.fromToken(requested)
?: error(
"Unknown -Pformat=$requested. Valid formats: " +
PackageFormat.entries.joinToString(", ") { it.token }
)
}
/** The installer format this build is packaged as, from `-Pformat`. */
fun Project.packageFormat(): PackageFormat = resolvePackageFormat(findProperty("format")?.toString())

View file

@ -1,145 +0,0 @@
package com.darkrockstudios.build
import org.gradle.api.Project
import java.io.File
import java.security.MessageDigest
import java.time.Instant
import java.time.temporal.ChronoUnit
import java.util.Base64
/** One attached installer as the update metadata describes it. */
data class UpdateAsset(val name: String, val size: Long, val sha512: String)
/**
* The metadata files the desktop updater reads, each listing its platform's installers in
* preference order. The format is electron-builder's `latest.yml`, which Nucleus parses; the
* `url` is the bare asset name and the client prefixes the release URL.
*/
private val UPDATE_METADATA_FILES: Map<String, List<String>> = mapOf(
"latest.yml" to listOf("hammer.msi", "hammer.exe"),
"latest-mac.yml" to listOf("hammer.zip", "hammer.dmg", "hammer.pkg"),
"latest-linux.yml" to listOf("hammer.deb", "hammer.rpm", "hammer.AppImage"),
)
/** Assets a release may legitimately lack; every other listed name must have a digest. */
private val OPTIONAL_ASSETS: Set<String> = setOf("hammer.zip")
/** Suffix of the sidecar each platform job writes next to its installer: `<base64 sha512> <bytes>`. */
const val DIGEST_SUFFIX = ".sha512"
private const val SHA512_BUFFER_SIZE = 1 shl 16
fun renderUpdateMetadata(version: String, releaseDate: String, assets: List<UpdateAsset>): String =
buildString {
appendLine("version: $version")
appendLine("releaseDate: $releaseDate")
appendLine("files:")
for (asset in assets) {
appendLine(" - url: ${asset.name}")
appendLine(" sha512: ${asset.sha512}")
appendLine(" size: ${asset.size}")
}
}
/** Base64 of the SHA-512 digest, which is how Nucleus's checksum verifier compares it. */
fun sha512Base64(file: File): String {
val digest = MessageDigest.getInstance("SHA-512")
file.inputStream().use { input ->
val buffer = ByteArray(SHA512_BUFFER_SIZE)
while (true) {
val read = input.read(buffer)
if (read < 0) break
digest.update(buffer, 0, read)
}
}
return Base64.getEncoder().encodeToString(digest.digest())
}
/** The one-line sidecar content for an installer. */
fun assetDigestLine(file: File): String = "${sha512Base64(file)} ${file.length()}"
fun parseAssetDigest(name: String, line: String): UpdateAsset {
val parts = line.trim().split(' ')
require(parts.size == 2) { "Digest for $name should be '<base64 sha512> <bytes>', got '$line'" }
val size = parts[1].toLongOrNull() ?: error("Digest for $name has a non-numeric size: '$line'")
return UpdateAsset(name = name, size = size, sha512 = parts[0])
}
/**
* Builds every metadata file from the digest sidecars anywhere under [digestsDir], keyed by file
* name: upload-artifact keeps each installer's format subdirectory. A required installer with no
* sidecar fails loudly: a yml that omits it would silently leave that format with no update path.
*/
fun updateMetadataFiles(digestsDir: File, version: String, releaseDate: String): Map<String, String> {
val sidecars = digestsDir.walk()
.filter { it.isFile && it.name.endsWith(DIGEST_SUFFIX) }
.groupBy { it.name }
.mapValues { (name, found) ->
found.singleOrNull() ?: error("$digestsDir has more than one $name: $found")
}
return UPDATE_METADATA_FILES.mapValues { (metadataFile, names) ->
val assets = names.mapNotNull { name ->
val sidecar = sidecars[name + DIGEST_SUFFIX]
when {
sidecar != null -> parseAssetDigest(name, sidecar.readText())
name in OPTIONAL_ASSETS -> null
else -> error("$metadataFile needs $name$DIGEST_SUFFIX, but $digestsDir has no such file")
}
}
renderUpdateMetadata(version, releaseDate, assets)
}
}
/**
* Registers the two halves of the update metadata:
*
* - `writeAssetDigests -PassetFiles=<path,path>` runs in each platform job right after it
* packages and renames its installers, writing `<installer>.sha512` beside each one. The
* sidecars travel to the final job as a workflow artifact, so no installer is downloaded
* twice and no release lookup is needed.
* - `writeUpdateMetadata -PreleaseDigestsDir=<dir>` runs once in the final job over the
* collected sidecars and writes the yml files to `build/update-metadata/`.
*/
fun Project.registerUpdateMetadataTasks(appVersion: String) {
tasks.register("writeAssetDigests") {
group = "distribution"
description = "Writes a <installer>.sha512 sidecar beside each installer in -PassetFiles (comma separated)."
doLast {
val files = findProperty("assetFiles")?.toString()
?.split(',')
?.map { it.trim() }
?.filter { it.isNotEmpty() }
?.takeIf { it.isNotEmpty() }
?: error("Pass -PassetFiles=<installer>[,<installer>...]")
for (path in files) {
val file = File(path)
check(file.isFile) { "No installer at $path" }
val sidecar = File(file.path + DIGEST_SUFFIX)
sidecar.writeText(assetDigestLine(file))
println("Wrote $sidecar")
}
}
}
tasks.register("writeUpdateMetadata") {
group = "distribution"
description = "Writes the latest*.yml files the desktop updater reads, from the digest sidecars in -PreleaseDigestsDir."
doLast {
val digestsDir = File(
findProperty("releaseDigestsDir")?.toString()
?: error("Pass -PreleaseDigestsDir=<directory holding the *$DIGEST_SUFFIX sidecars>")
)
val outputDir = layout.buildDirectory.dir("update-metadata").get().asFile
outputDir.mkdirs()
val releaseDate = Instant.now().truncatedTo(ChronoUnit.SECONDS).toString()
for ((name, content) in updateMetadataFiles(digestsDir, appVersion, releaseDate)) {
val target = outputDir.resolve(name)
target.writeText(content)
println("Wrote $target")
}
}
}
}

View file

@ -1,34 +0,0 @@
import com.darkrockstudios.build.PackageFormat
import com.darkrockstudios.build.resolvePackageFormat
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class PackageFormatTest {
@Test
fun `no format property means not packaged`() {
assertEquals(PackageFormat.NONE, resolvePackageFormat(null))
assertEquals(PackageFormat.NONE, resolvePackageFormat(""))
assertEquals(PackageFormat.NONE, resolvePackageFormat(" "))
}
@Test
fun `every token resolves to its own format`() {
for (format in PackageFormat.entries) {
assertEquals(format, resolvePackageFormat(format.token))
}
}
@Test
fun `an unknown token fails the build`() {
val error = assertFailsWith<IllegalStateException> { resolvePackageFormat("windows") }
assertEquals(true, error.message?.contains("Unknown -Pformat=windows"))
}
@Test
fun `tokens are unique`() {
val tokens = PackageFormat.entries.map { it.token }
assertEquals(tokens.toSet().size, tokens.size)
}
}

View file

@ -1,140 +0,0 @@
import com.darkrockstudios.build.DIGEST_SUFFIX
import com.darkrockstudios.build.UpdateAsset
import com.darkrockstudios.build.assetDigestLine
import com.darkrockstudios.build.parseAssetDigest
import com.darkrockstudios.build.renderUpdateMetadata
import com.darkrockstudios.build.sha512Base64
import com.darkrockstudios.build.updateMetadataFiles
import java.io.File
import java.nio.file.Files
import java.security.MessageDigest
import java.util.Base64
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNull
class UpdateMetadataTest {
private val installers = listOf(
"hammer.msi", "hammer.exe", "hammer.dmg", "hammer.pkg", "hammer.deb", "hammer.rpm", "hammer.AppImage",
)
@Test
fun `renders the electron-builder layout Nucleus parses`() {
val yml = renderUpdateMetadata(
version = "3.12.0",
releaseDate = "2026-10-20T18:04:11Z",
assets = listOf(
UpdateAsset("hammer.msi", 190034708, "abc="),
UpdateAsset("hammer.exe", 190100000, "def="),
),
)
val expected = """
|version: 3.12.0
|releaseDate: 2026-10-20T18:04:11Z
|files:
| - url: hammer.msi
| sha512: abc=
| size: 190034708
| - url: hammer.exe
| sha512: def=
| size: 190100000
|""".trimMargin()
assertEquals(expected, yml)
}
@Test
fun `sha512 is base64 of the digest`() {
val file = tempDir().resolve("asset.bin").apply { writeBytes(ByteArray(100_000) { it.toByte() }) }
val expected = Base64.getEncoder().encodeToString(
MessageDigest.getInstance("SHA-512").digest(file.readBytes())
)
assertEquals(expected, sha512Base64(file))
}
@Test
fun `a digest line round-trips through the sidecar`() {
val file = tempDir().resolve("hammer.msi").apply { writeBytes(ByteArray(12_345) { (it * 3).toByte() }) }
val asset = parseAssetDigest("hammer.msi", assetDigestLine(file) + "\n")
assertEquals(UpdateAsset("hammer.msi", 12_345, sha512Base64(file)), asset)
}
@Test
fun `a malformed sidecar is rejected`() {
assertFailsWith<IllegalArgumentException> { parseAssetDigest("hammer.msi", "abc=") }
assertFailsWith<IllegalStateException> { parseAssetDigest("hammer.msi", "abc= many") }
}
@Test
fun `every platform file lists its installers from the sidecars`() {
val dir = tempDir()
installers.forEachIndexed { i, name -> dir.sidecar(name, "digest$i= ${100 + i}") }
val files = updateMetadataFiles(dir, "3.12.0", "2026-10-20T18:04:11Z")
assertEquals(setOf("latest.yml", "latest-mac.yml", "latest-linux.yml"), files.keys)
assertEquals(listOf("hammer.msi", "hammer.exe"), urlsIn(files.getValue("latest.yml")))
assertEquals(listOf("hammer.dmg", "hammer.pkg"), urlsIn(files.getValue("latest-mac.yml")))
assertEquals(
listOf("hammer.deb", "hammer.rpm", "hammer.AppImage"),
urlsIn(files.getValue("latest-linux.yml")),
)
assertEquals(" sha512: digest0=", files.getValue("latest.yml").lines()[4])
assertEquals(" size: 100", files.getValue("latest.yml").lines()[5])
}
@Test
fun `sidecars are found inside the per-format subdirectories the artifacts keep`() {
val dir = tempDir()
installers.forEach { dir.resolve(it.substringAfter('.').lowercase()).apply { mkdirs() }.sidecar(it, "x= 1") }
val files = updateMetadataFiles(dir, "1", "d")
assertEquals(listOf("hammer.msi", "hammer.exe"), urlsIn(files.getValue("latest.yml")))
assertEquals(listOf("hammer.deb", "hammer.rpm", "hammer.AppImage"), urlsIn(files.getValue("latest-linux.yml")))
}
@Test
fun `a duplicated sidecar fails instead of picking one`() {
val dir = tempDir()
installers.forEach { dir.sidecar(it, "x= 1") }
dir.resolve("msi").apply { mkdirs() }.sidecar("hammer.msi", "y= 2")
assertFailsWith<IllegalStateException> { updateMetadataFiles(dir, "1", "d") }
}
@Test
fun `the mac zip is listed first when present and skipped when absent`() {
val dir = tempDir()
installers.forEach { dir.sidecar(it, "x= 1") }
assertNull(urlsIn(updateMetadataFiles(dir, "1", "d").getValue("latest-mac.yml")).firstOrNull { it == "hammer.zip" })
dir.sidecar("hammer.zip", "z= 2")
assertEquals(
listOf("hammer.zip", "hammer.dmg", "hammer.pkg"),
urlsIn(updateMetadataFiles(dir, "1", "d").getValue("latest-mac.yml")),
)
}
@Test
fun `a missing required installer fails instead of publishing a gap`() {
val dir = tempDir()
installers.filter { it != "hammer.exe" }.forEach { dir.sidecar(it, "x= 1") }
val error = assertFailsWith<IllegalStateException> { updateMetadataFiles(dir, "1", "d") }
assertEquals(true, error.message?.startsWith("latest.yml needs hammer.exe$DIGEST_SUFFIX"))
}
private fun File.sidecar(name: String, line: String) = resolve(name + DIGEST_SUFFIX).writeText(line)
private fun urlsIn(yml: String): List<String> =
yml.lines().filter { it.trim().startsWith("- url:") }.map { it.substringAfter("- url:").trim() }
private fun tempDir(): File = Files.createTempDirectory("update-metadata-test").toFile().apply { deleteOnExit() }
}

View file

@ -1,150 +0,0 @@
package com.darkrockstudios.apps.hammer.common.data.globalsettings.datasource
import android.content.SharedPreferences
import kotlinx.serialization.json.Json
import javax.crypto.AEADBadTagException
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
class EncryptedSharedPrefsAuthTokenStoreTest {
private val disk = FakeEncryptedPrefsDisk()
private fun createStore() = EncryptedSharedPrefsAuthTokenStore(
json = Json,
openPrefs = disk::open,
resetPrefs = disk::reset,
)
@Test
fun `tokens round trip`() {
val store = createStore()
store.put(URL, USER_ID, TOKENS)
assertEquals(TOKENS, createStore().get(URL, USER_ID))
}
@Test
fun `keyset the master key cannot decrypt is treated as no tokens`() {
disk.keysetDecryptable = false
val store = createStore()
val tokens = store.get(URL, USER_ID)
assertNull(tokens)
}
@Test
fun `store is usable again after an undecryptable keyset`() {
disk.keysetDecryptable = false
createStore().get(URL, USER_ID)
createStore().put(URL, USER_ID, TOKENS)
assertEquals(TOKENS, createStore().get(URL, USER_ID))
}
@Test
fun `value that cannot be decrypted is treated as no tokens`() {
createStore().put(URL, USER_ID, TOKENS)
disk.valuesDecryptable = false
val tokens = createStore().get(URL, USER_ID)
assertNull(tokens)
}
@Test
fun `put replaces a value that cannot be decrypted`() {
createStore().put(URL, USER_ID, TOKENS)
disk.valuesDecryptable = false
val replacement = AuthTokens(bearerToken = "new-bearer", refreshToken = "new-refresh")
createStore().put(URL, USER_ID, replacement)
assertEquals(replacement, createStore().get(URL, USER_ID))
}
companion object {
private const val URL = "https://hammer.example"
private const val USER_ID = 7L
private val TOKENS = AuthTokens(bearerToken = "bearer", refreshToken = "refresh")
}
}
/**
* Models the encrypted prefs file: [open] fails like EncryptedSharedPreferences.create
* when the Keystore master key cannot decrypt the stored keyset, and [reset] removes
* the file (keyset included) so the next open generates a fresh one.
*/
private class FakeEncryptedPrefsDisk {
val values = mutableMapOf<String, String>()
var keysetDecryptable = true
var valuesDecryptable = true
fun open(): SharedPreferences {
if (!keysetDecryptable) throw AEADBadTagException()
return FakePrefs()
}
fun reset() {
values.clear()
keysetDecryptable = true
valuesDecryptable = true
}
private inner class FakePrefs : SharedPreferences {
override fun getString(key: String, defValue: String?): String? {
val stored = values[key] ?: return defValue
if (!valuesDecryptable) throw SecurityException("Could not decrypt value")
return stored
}
override fun contains(key: String): Boolean = key in values
override fun getAll(): MutableMap<String, *> = values.toMutableMap()
override fun edit(): SharedPreferences.Editor = FakeEditor()
override fun getStringSet(key: String, defValues: MutableSet<String>?) = unsupported()
override fun getInt(key: String, defValue: Int): Int = unsupported()
override fun getLong(key: String, defValue: Long): Long = unsupported()
override fun getFloat(key: String, defValue: Float): Float = unsupported()
override fun getBoolean(key: String, defValue: Boolean): Boolean = unsupported()
override fun registerOnSharedPreferenceChangeListener(
listener: SharedPreferences.OnSharedPreferenceChangeListener
) = unsupported()
override fun unregisterOnSharedPreferenceChangeListener(
listener: SharedPreferences.OnSharedPreferenceChangeListener
) = unsupported()
}
private inner class FakeEditor : SharedPreferences.Editor {
private val pending = mutableMapOf<String, String?>()
private var clearAll = false
override fun putString(key: String, value: String?) = apply { pending[key] = value }
override fun remove(key: String) = apply { pending[key] = null }
override fun clear() = apply { clearAll = true }
override fun commit(): Boolean {
if (clearAll) values.clear()
pending.forEach { (key, value) -> if (value == null) values.remove(key) else values[key] = value }
valuesDecryptable = true
return true
}
override fun apply() {
commit()
}
override fun putStringSet(key: String, values: MutableSet<String>?) = unsupported()
override fun putInt(key: String, value: Int) = unsupported()
override fun putLong(key: String, value: Long) = unsupported()
override fun putFloat(key: String, value: Float) = unsupported()
override fun putBoolean(key: String, value: Boolean) = unsupported()
}
}
private fun unsupported(): Nothing = throw UnsupportedOperationException()

View file

@ -9,46 +9,31 @@ import io.github.aakira.napier.Napier
import kotlinx.atomicfu.locks.reentrantLock
import kotlinx.atomicfu.locks.withLock
import kotlinx.serialization.json.Json
import java.io.IOException
import java.security.GeneralSecurityException
import java.security.KeyStore
/**
* [AuthTokenStore] backed by [EncryptedSharedPreferences] with an Android
* Keystore-backed master key. The account-keyed token map is stored as JSON in a
* single encrypted preferences entry.
*/
class EncryptedSharedPrefsAuthTokenStore internal constructor(
class EncryptedSharedPrefsAuthTokenStore(
context: Context,
private val json: Json,
private val openPrefs: () -> SharedPreferences,
private val resetPrefs: () -> Unit,
) : AuthTokenStore {
constructor(context: Context, json: Json) : this(
json = json,
openPrefs = { openEncryptedPrefs(context) },
resetPrefs = { resetEncryptedPrefs(context) },
)
private val lock = reentrantLock()
private val prefs: SharedPreferences by lazy {
try {
openPrefs()
} catch (e: GeneralSecurityException) {
recreatePrefs(e)
} catch (e: IOException) {
recreatePrefs(e)
}
}
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
// The keyset lives in the prefs file but its master key lives in the Keystore, which is
// never backed up, so a restored or orphaned file is undecryptable, and some keystores
// leave the master key itself unusable. Start over with both (forces re-login).
private fun recreatePrefs(cause: Exception): SharedPreferences {
Napier.w("Auth token keyset is unreadable; recreating the store", cause)
resetPrefs()
return openPrefs()
EncryptedSharedPreferences.create(
context,
PREFS_NAME,
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
)
}
override fun get(url: String, userId: Long): AuthTokens? = lock.withLock {
@ -69,11 +54,11 @@ class EncryptedSharedPrefsAuthTokenStore internal constructor(
}
private fun loadMap(): Map<String, AuthTokens> {
val stored = prefs.getString(TOKENS_KEY, null) ?: return emptyMap()
return try {
val stored = prefs.getString(TOKENS_KEY, null) ?: return emptyMap()
json.decodeFromString<Map<String, AuthTokens>>(stored)
} catch (@Suppress("TooGenericExceptionCaught") e: Exception) {
Napier.w("Failed to read encrypted auth tokens; treating as empty", e)
Napier.w("Failed to parse encrypted auth tokens; treating as empty", e)
emptyMap()
}
}
@ -85,26 +70,5 @@ class EncryptedSharedPrefsAuthTokenStore internal constructor(
companion object {
private const val PREFS_NAME = "hammer_auth_tokens"
private const val TOKENS_KEY = "tokens"
private const val ANDROID_KEYSTORE = "AndroidKeyStore"
private fun openEncryptedPrefs(context: Context): SharedPreferences {
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
return EncryptedSharedPreferences.create(
context,
PREFS_NAME,
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
)
}
private fun resetEncryptedPrefs(context: Context) {
context.deleteSharedPreferences(PREFS_NAME)
KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) }
.deleteEntry(MasterKey.DEFAULT_MASTER_KEY_ALIAS)
}
}
}

View file

@ -1,8 +1,5 @@
package com.darkrockstudios.apps.hammer.common.dependencyinjection
import com.darkrockstudios.apps.hammer.common.data.appupdate.AppUpdater
import com.darkrockstudios.apps.hammer.common.data.appupdate.NoOpAppUpdater
import com.darkrockstudios.apps.hammer.common.components.projectselection.accountsettings.AndroidPlatformSettingsComponent
import com.darkrockstudios.apps.hammer.common.components.projectselection.accountsettings.PlatformSettings
import com.darkrockstudios.apps.hammer.common.components.storyeditor.focusmode.FocusModeService
@ -39,5 +36,4 @@ actual val platformModule = module {
single<AndroidShareService>()
single<BackupManagerService>()
single<GlobalSettingsDatasource> { get<GlobalSettingsFilesystemDatasource>() }
single<AppUpdater> { NoOpAppUpdater }
}

View file

@ -1,32 +1,17 @@
## [3.12.0] - 2026-10-5
## [3.11.0] - 2026-9-24
New features
- Reader Mode: read your story as prose, one chapter at a time, instead of through the scene editor. Open it from the Read button under the scene list, the book icon in the scene editor, or the editor menu. The scene list becomes a table of contents, and the reader has its own font size
- Export: three new options in the Export Story dialog. Number chapter titles, pick a font (Word, RTF and EPUB), and choose whether to keep blank lines between paragraphs
- Android and iOS: an OLED theme, the dark theme on a pure black background
- Desktop (Windows and Linux installs from GitHub): Hammer tells you when a newer release is out and can update itself from the project list or the About screen. macOS DMG installs are told and sent to the release page. Automatic checks can be turned off in Settings
- Text editor 3.0: Huge improvements in input and performance
- Text editor: change the text size with Ctrl/Cmd and Plus, Minus or 0, or from the format bar's overflow menu
- Text editor: Delete in the right-click menu
- Turkish translation
- Web: a fifth reader reaction on public stories, "Surprised me"
- Web: signed-in readers can leave kudos at the end of a publicly published story, picking up to four craft chips and one reaction. Kudos are anonymous: authors see per-chip totals on their story page, and a chip appears on the public page once three readers have picked it. Authors can turn kudos off for any story
Improvements
- Syncing with unsaved work now asks Save, Discard or Cancel first, where before it silently saved every unsaved scene
- Open notes, timeline events, encyclopedia entries and the scene title, outline and notes refresh when a sync or conflict resolution changes them. An edit in progress keeps its draft
- The Backup Manager lists backups of projects that are no longer on disk, so a deleted project can be restored
- Exports no longer number chapter titles unless you turn the new option on. Word, RTF, PDF and Markdown used to number them always
- Text editor: the heading button writes real Markdown headings, and Hammer's shortcuts land on the same keys on non-QWERTY layouts
- Android: a launch splash screen on every supported version
- Word counts no longer count standalone Markdown markers such as horizontal rules, heading marks, bullets and blockquote markers as words. The server now counts the same way, so both give the same number
- Android and Wear OS builds are now shrunk and obfuscated with R8
- Translations and dependencies updated
Fixes
- Android: unsaved scene edits could be lost when the app was closed in the background. They now come back as unsaved scenes the next time the project opens [#983]
- Export: unsaved scene edits were left out of the exported file [#983]
- Restoring a backup from the Backup Manager made the project disappear from the project list
- Choosing Discard on close with automatic sync on still saved and uploaded the discarded scenes
- PDF export: long paragraphs ran off the bottom of the page or left large blank gaps
- Android: a crash on launch when the saved login could not be decrypted, for example after restoring the device from a backup. You are asked to sign in again
- iOS: a crash when tapping into a text editor
- Server URL, number, tag and word-list fields no longer autocorrect or capitalize, which put a space after each "." in a server URL
- Encyclopedia references no longer match a name inside a longer word with accented or non-Latin letters ("Ana" in "Anaïs")
- Text editor: numpad navigation keys and numpad Enter now work
- Text editor: keyboard (IME) edits are applied right away, and the keyboard is told about each one only once
- Text editor: spelling underlines stay on the right words while you keep typing
- Text editor: the formatting you picked for new text is kept after unrelated updates
- Text editor: fixed crashes and misplaced carets when drawing the cursor or moving it during a layout change
- Wear OS: pairing with the phone app works in release builds

View file

@ -22,7 +22,6 @@
<string name="confirm_dialog_negative">Nein</string>
<string name="expand">Erweitern</string>
<string name="collapse">Einklappen</string>
<string name="markdown_format_bar_text_size">Text size</string>
<string name="markdown_format_bar_decrease_text_size">Textgröße verringern</string>
<string name="markdown_format_bar_increase_text_size">Textgröße vergrößern</string>
<string name="markdown_format_bar_reset_text_size">Textgröße zurücksetzen</string>

View file

@ -12,17 +12,6 @@
<string name="about_attribution_studio_button">Dark Rock Studios</string>
<string name="about_version_changes_button">Changes</string>
<string name="about_version_github_button">GitHub Release</string>
<string name="about_update_check_button">Check for Updates</string>
<string name="about_update_checking">Checking for updates&#8230;</string>
<string name="about_update_up_to_date">Hammer is up to date.</string>
<string name="about_update_available">Hammer %1$s is available.</string>
<string name="about_update_install_button">Update to %1$s</string>
<string name="about_update_downloading">Downloading Hammer %1$s (%2$s)</string>
<string name="about_update_installing">Installing Hammer %1$s. Hammer will restart.</string>
<string name="about_update_failed">Could not update to %1$s: %2$s</string>
<string name="about_update_retry_button">Retry</string>
<string name="about_update_later_button">Later</string>
<string name="about_update_quit_notice">Hammer will quit to install the update, then reopen.</string>
<string name="changelog_dialog_title">What's New</string>
<string name="update_dialog_release_notes_section">Versionshinweise</string>
<string name="update_dialog_release_notes_empty">Keine Versionshinweise vorhanden.</string>

View file

@ -9,7 +9,6 @@
<string name="settings_theme_label">Farbschema</string>
<string name="settings_theme_light">HELL</string>
<string name="settings_theme_dark">DUNKEL</string>
<string name="settings_theme_oled_black">OLED</string>
<string name="settings_theme_followsystem">SYSTEM</string>
<string name="settings_initial_screen_label">Anfangsbildschirm des Projekts</string>
<string name="settings_initial_screen_home">Startseite</string>
@ -56,8 +55,6 @@
<string name="merge_projects_replace_confirm_title">Delete all local projects?</string>
<string name="merge_projects_replace_confirm_message">This permanently deletes every project on this device and replaces them with the projects from the server. This cannot be undone.</string>
<string name="merge_projects_replace_confirm_button">Delete and Replace</string>
<string name="settings_automatic_update_checks">Check for updates automatically</string>
<string name="settings_automatic_update_checks_hint">Looks for a new release on GitHub shortly after launch and every few hours. Nothing is downloaded or installed until you choose to update.</string>
<string name="settings_platform_settings_title">Plattform-Einstellungen</string>
<string name="settings_platform_settings_empty">Keine plattformspezifischen Einstellungen.</string>
<string name="settings_keep_screen_on">Bildschirm nicht ausschalten</string>
@ -86,7 +83,6 @@
<string name="backup_manager_error_load_backups">Fehler beim Laden der Backups: %1$s</string>
<string name="backup_manager_error_load_project_backups">Fehler beim Laden der Backups für das Projekt: %1$s</string>
<string name="backup_manager_error_delete_backup">Fehler beim Löschen der Backups: %1$s</string>
<string name="backup_manager_error_restore_backup">Failed to restore backup: %1$s</string>
<string name="backup_restore_dialog_title">Backup wiederherstellen</string>
<string name="backup_restore_dialog_message">Warnung: Das Wiederherstellen dieses Backups ersetzt den aktuellen Inhalt des Projekts vollständig. Alle vorhandenen Dateien werden gelöscht und mit den Backup-Daten ersetzt. Diese Aktion kann nicht rückgängig gemacht werden.</string>
<string name="backup_restore_dialog_confirm">Wiederherstellen</string>

View file

@ -78,10 +78,6 @@
<string name="project_home_export_format_rtf">RTF (.rtf)</string>
<string name="project_home_export_cancel">Abbrechen</string>
<string name="project_home_export_execute">Exportieren</string>
<string name="project_home_export_number_chapters_label">Number chapter titles</string>
<string name="project_home_export_font_label">Font</string>
<string name="project_home_export_blank_lines_label">Keep blank lines between paragraphs</string>
<string name="project_home_export_blank_lines_hint">Turn off if you separate paragraphs with a blank line and do not want empty paragraphs in the export. Scene breaks and runs of two or more blank lines are kept.</string>
<string name="project_home_export_limit_scenes_label">Limit to specific scenes</string>
<string name="project_home_export_limit_scenes_hint">Only the selected scenes will be included in the export</string>
<string name="project_home_export_scenes_label">Szenen</string>

View file

@ -7,7 +7,6 @@
<string name="projects_list_create_button">Projekt erstellen</string>
<string name="projects_list_toast_sync_complete">Projekte synchronisiert</string>
<string name="projects_list_toast_sync_failed">Fehler beim Synchronisieren der Projekte</string>
<string name="projects_list_toast_delete_failed">Could not delete the project. Check that its folder is accessible and try again.</string>
<string name="projects_list_item_more_button">Mehr</string>
<string name="projects_list_card_words">%1$s Wörter</string>
<string name="projects_list_card_by_author">von %1$s</string>

View file

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="reader_mode_section">Reader</string>
<string name="reader_mode_position">Chapter %1$d of %2$d</string>
<string name="reader_mode_edit">Edit ↗</string>
<string name="reader_mode_close">× Close</string>
<string name="reader_mode_decrease_font_size">Textgröße verringern</string>
<string name="reader_mode_increase_font_size">Textgröße vergrößern</string>
<string name="reader_mode_reset_font_size">Textgröße zurücksetzen</string>
<string name="reader_mode_toggle_scene_headings">Show scene headings</string>
<string name="reader_mode_previous_chapter">← Previous chapter</string>
<string name="reader_mode_next_chapter">Next chapter →</string>
<string name="reader_mode_empty_chapter">This chapter has no scenes yet</string>
<string name="reader_mode_empty_story">There is nothing to read yet</string>
<string name="reader_mode_end">End of story</string>
</resources>

View file

@ -24,7 +24,6 @@
<string name="scene_editor_menu_item_archive">Archivieren</string>
<string name="scene_editor_menu_item_save">Speichern</string>
<string name="scene_editor_menu_item_close">Schließen</string>
<string name="scene_editor_menu_item_read">Read</string>
<string name="scene_editor_add_to_dictionary">Add to dictionary</string>
<string name="archive_scene_dialog_title">Szene archivieren</string>
<string name="archive_scene_dialog_message">Möchten Sie '%1$s' wirklich archivieren? Die Szene wird aus der Szenenliste ausgeblendet, kann aber später wiederhergestellt werden.</string>

View file

@ -16,8 +16,7 @@
<string name="create_sceneitem_dialog_create_button">Erstellen</string>
<string name="create_sceneitem_dialog_dismiss_button">Abbrechen</string>
<string name="scene_list_outline_overview_title">Gliederung</string>
<string name="scene_list_outline_button">Gliederung</string>
<string name="scene_list_reader_button">Read</string>
<string name="scene_list_outline_overview_button">Gliederungsübersicht</string>
<string name="scene_list_outline_overview_none">&lt;keine Gliederung&gt;</string>
<string name="scene_list_outline_dismiss">Schließen</string>
<string name="scene_list_outline_overview_eyebrow">Manuskript</string>

View file

@ -22,7 +22,6 @@
<string name="confirm_dialog_negative">No</string>
<string name="expand">Expandir</string>
<string name="collapse">Contraer</string>
<string name="markdown_format_bar_text_size">Text size</string>
<string name="markdown_format_bar_decrease_text_size">Reducir el tamaño del texto</string>
<string name="markdown_format_bar_increase_text_size">Aumentar el tamaño del texto</string>
<string name="markdown_format_bar_reset_text_size">Restablecer el tamaño del texto</string>

View file

@ -12,17 +12,6 @@
<string name="about_attribution_studio_button">Dark Rock Studios</string>
<string name="about_version_changes_button">Changes</string>
<string name="about_version_github_button">GitHub Release</string>
<string name="about_update_check_button">Check for Updates</string>
<string name="about_update_checking">Checking for updates&#8230;</string>
<string name="about_update_up_to_date">Hammer is up to date.</string>
<string name="about_update_available">Hammer %1$s is available.</string>
<string name="about_update_install_button">Update to %1$s</string>
<string name="about_update_downloading">Downloading Hammer %1$s (%2$s)</string>
<string name="about_update_installing">Installing Hammer %1$s. Hammer will restart.</string>
<string name="about_update_failed">Could not update to %1$s: %2$s</string>
<string name="about_update_retry_button">Retry</string>
<string name="about_update_later_button">Later</string>
<string name="about_update_quit_notice">Hammer will quit to install the update, then reopen.</string>
<string name="changelog_dialog_title">What's New</string>
<string name="update_dialog_release_notes_section">Notas de la versión</string>
<string name="update_dialog_release_notes_empty">No se proporcionaron notas de la versión.</string>

View file

@ -9,7 +9,6 @@
<string name="settings_theme_label">Tema de la interfaz</string>
<string name="settings_theme_light">CLARO</string>
<string name="settings_theme_dark">OSCURO</string>
<string name="settings_theme_oled_black">OLED</string>
<string name="settings_theme_followsystem">SISTEMA</string>
<string name="settings_initial_screen_label">Pantalla inicial del proyecto</string>
<string name="settings_initial_screen_home">Inicio</string>
@ -56,8 +55,6 @@
<string name="merge_projects_replace_confirm_title">Delete all local projects?</string>
<string name="merge_projects_replace_confirm_message">This permanently deletes every project on this device and replaces them with the projects from the server. This cannot be undone.</string>
<string name="merge_projects_replace_confirm_button">Delete and Replace</string>
<string name="settings_automatic_update_checks">Check for updates automatically</string>
<string name="settings_automatic_update_checks_hint">Looks for a new release on GitHub shortly after launch and every few hours. Nothing is downloaded or installed until you choose to update.</string>
<string name="settings_platform_settings_title">Ajustes de la plataforma</string>
<string name="settings_platform_settings_empty">No hay ajustes específicos de la plataforma.</string>
<string name="settings_keep_screen_on">Mantener la pantalla encendida</string>
@ -86,7 +83,6 @@
<string name="backup_manager_error_load_backups">No se pudieron cargar las copias de seguridad: %1$s</string>
<string name="backup_manager_error_load_project_backups">No se pudieron cargar las copias de seguridad del proyecto: %1$s</string>
<string name="backup_manager_error_delete_backup">No se pudo eliminar la copia de seguridad: %1$s</string>
<string name="backup_manager_error_restore_backup">Failed to restore backup: %1$s</string>
<string name="backup_restore_dialog_title">Restaurar copia de seguridad</string>
<string name="backup_restore_dialog_message">Advertencia: restaurar esta copia de seguridad reemplazará por completo el contenido actual del proyecto. Todos los archivos existentes se eliminarán y se reemplazarán con los datos de la copia de seguridad. Esta acción no se puede deshacer.</string>
<string name="backup_restore_dialog_confirm">Restaurar</string>

View file

@ -78,10 +78,6 @@
<string name="project_home_export_format_rtf">RTF (.rtf)</string>
<string name="project_home_export_cancel">Cancelar</string>
<string name="project_home_export_execute">Exportar</string>
<string name="project_home_export_number_chapters_label">Number chapter titles</string>
<string name="project_home_export_font_label">Font</string>
<string name="project_home_export_blank_lines_label">Keep blank lines between paragraphs</string>
<string name="project_home_export_blank_lines_hint">Turn off if you separate paragraphs with a blank line and do not want empty paragraphs in the export. Scene breaks and runs of two or more blank lines are kept.</string>
<string name="project_home_export_limit_scenes_label">Limit to specific scenes</string>
<string name="project_home_export_limit_scenes_hint">Only the selected scenes will be included in the export</string>
<string name="project_home_export_scenes_label">Escenas</string>

View file

@ -7,7 +7,6 @@
<string name="projects_list_create_button">Crear proyecto</string>
<string name="projects_list_toast_sync_complete">Proyectos sincronizados</string>
<string name="projects_list_toast_sync_failed">No se pudieron sincronizar los proyectos</string>
<string name="projects_list_toast_delete_failed">Could not delete the project. Check that its folder is accessible and try again.</string>
<string name="projects_list_item_more_button">Más</string>
<string name="projects_list_card_words">%1$s palabras</string>
<string name="projects_list_card_by_author">por %1$s</string>

View file

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="reader_mode_section">Reader</string>
<string name="reader_mode_position">Chapter %1$d of %2$d</string>
<string name="reader_mode_edit">Edit ↗</string>
<string name="reader_mode_close">× Close</string>
<string name="reader_mode_decrease_font_size">Reducir el tamaño del texto</string>
<string name="reader_mode_increase_font_size">Aumentar el tamaño del texto</string>
<string name="reader_mode_reset_font_size">Restablecer el tamaño del texto</string>
<string name="reader_mode_toggle_scene_headings">Show scene headings</string>
<string name="reader_mode_previous_chapter">← Previous chapter</string>
<string name="reader_mode_next_chapter">Next chapter →</string>
<string name="reader_mode_empty_chapter">This chapter has no scenes yet</string>
<string name="reader_mode_empty_story">There is nothing to read yet</string>
<string name="reader_mode_end">End of story</string>
</resources>

View file

@ -24,7 +24,6 @@
<string name="scene_editor_menu_item_archive">Archivar</string>
<string name="scene_editor_menu_item_save">Guardar</string>
<string name="scene_editor_menu_item_close">Cerrar</string>
<string name="scene_editor_menu_item_read">Read</string>
<string name="scene_editor_add_to_dictionary">Add to dictionary</string>
<string name="archive_scene_dialog_title">Archivar escena</string>
<string name="archive_scene_dialog_message">¿Seguro que quieres archivar «%1$s»? La escena se ocultará de la lista de escenas, pero podrás restaurarla más tarde.</string>

View file

@ -16,8 +16,7 @@
<string name="create_sceneitem_dialog_create_button">Crear</string>
<string name="create_sceneitem_dialog_dismiss_button">Cancelar</string>
<string name="scene_list_outline_overview_title">Esquema de la historia</string>
<string name="scene_list_outline_button">Esquema</string>
<string name="scene_list_reader_button">Read</string>
<string name="scene_list_outline_overview_button">Vista del esquema</string>
<string name="scene_list_outline_overview_none">&lt;sin esquema&gt;</string>
<string name="scene_list_outline_dismiss">Descartar</string>
<string name="scene_list_outline_overview_eyebrow">Manuscrito</string>

View file

@ -22,7 +22,6 @@
<string name="confirm_dialog_negative">Non</string>
<string name="expand">Développer</string>
<string name="collapse">Réduire</string>
<string name="markdown_format_bar_text_size">Taille du texte</string>
<string name="markdown_format_bar_decrease_text_size">Réduire la taille du texte</string>
<string name="markdown_format_bar_increase_text_size">Augmenter la taille du texte</string>
<string name="markdown_format_bar_reset_text_size">Réinitialiser la taille du texte</string>

View file

@ -12,17 +12,6 @@
<string name="about_attribution_studio_button">Dark Rock Studios</string>
<string name="about_version_changes_button">Changements</string>
<string name="about_version_github_button">Version GitHub</string>
<string name="about_update_check_button">Vérifier les mises à jour</string>
<string name="about_update_checking">Recherche de mises à jour&#8230;</string>
<string name="about_update_up_to_date">Hammer est à jour.</string>
<string name="about_update_available">Hammer %1$s est disponible.</string>
<string name="about_update_install_button">Mettre à jour ver %1$s</string>
<string name="about_update_downloading">Télécharger Hammer %1$s (%2$s)</string>
<string name="about_update_installing">Installation de Hammer %1$s. Hammer va redémarrer.</string>
<string name="about_update_failed">Impossible de mettre à jour vers la %1$s: %2$s</string>
<string name="about_update_retry_button">Réessayer</string>
<string name="about_update_later_button">Plus tard</string>
<string name="about_update_quit_notice">Hammer va se fermer pour installer la mise à jour, puis se rouvrira.</string>
<string name="changelog_dialog_title">Nouveautés</string>
<string name="update_dialog_release_notes_section">Notes de version</string>
<string name="update_dialog_release_notes_empty">Aucune note de version publiée.</string>
@ -32,9 +21,9 @@
<string name="about_logs_export_button">Exporter les journaux</string>
<string name="about_logs_no_logs">Aucun fichier journal trouvé</string>
<string name="about_logs_open_tooltip">Ouvrir le dossier des journaux</string>
<string name="about_logs_export_success">Journeaux exportés.</string>
<string name="about_logs_export_failed">Impossible d'exporter les journeaux. Voir le répertoire des journeaux.</string>
<string name="about_logs_copy_diagnostics_button">Copier les diagnostics</string>
<string name="about_logs_copy_diagnostics_success">Les diagnostics ont été copier dans le presse-papiers.</string>
<string name="about_logs_copy_diagnostics_failed">Impossible de copier les diagnostics.</string>
<string name="about_logs_export_success">Logs exported.</string>
<string name="about_logs_export_failed">Could not export the logs. See the log directory.</string>
<string name="about_logs_copy_diagnostics_button">Copy Diagnostics</string>
<string name="about_logs_copy_diagnostics_success">Diagnostics copied to the clipboard.</string>
<string name="about_logs_copy_diagnostics_failed">Could not copy the diagnostics.</string>
</resources>

View file

@ -9,7 +9,6 @@
<string name="settings_theme_label">Thème de l'interface</string>
<string name="settings_theme_light">CLAIR</string>
<string name="settings_theme_dark">SOMBRE</string>
<string name="settings_theme_oled_black">OLED</string>
<string name="settings_theme_followsystem">SYSTÈME</string>
<string name="settings_initial_screen_label">Écran initial du projet</string>
<string name="settings_initial_screen_home">Accueil</string>
@ -37,10 +36,10 @@
<string name="settings_remove_server_dialog_message">Voulez-vous vraiment supprimer le serveur ?</string>
<string name="settings_server_setup_title">Configurer le serveur</string>
<string name="settings_server_setup_url_hint">URL du serveur : hammer.ink</string>
<string name="settings_server_setup_insecure_warning">HTTP en clair : votre email, mot de passe et vos écrits non chiffrés. Utilisez cela uniquement sur un réseau de confiance.</string>
<string name="settings_server_setup_insecure_warning">Plain HTTP: your email, password and writing travel unencrypted. Only use this on a network you trust.</string>
<string name="settings_server_setup_email_hint">E-mail : utilisateur@exemple.com</string>
<string name="settings_server_setup_password_hint">Mot de passe</string>
<string name="settings_server_setup_password_requirements">%1$d-%2$d personnages</string>
<string name="settings_server_setup_password_requirements">%1$d-%2$d characters</string>
<string name="settings_server_setup_password_show">Afficher le mot de passe</string>
<string name="settings_server_setup_password_hide">Masquer le mot de passe</string>
<string name="settings_server_setup_login_button">Se connecter</string>
@ -53,11 +52,9 @@
<string name="merge_projects_explain_merge">Hammer fera correspondre vos projets locaux aux projets déjà présents sur le serveur. Les projets qui n'existent que sur le serveur seront téléchargés, et les projets présents en local seront téléversés sur le serveur.</string>
<string name="merge_projects_explain_replace">Tous vos projets locaux seront supprimés et remplacés par ceux présents sur le serveur.</string>
<string name="merge_projects_continue_button">Continuer</string>
<string name="merge_projects_replace_confirm_title">Supprimer tous les projets locaux ?</string>
<string name="merge_projects_replace_confirm_message">Cela supprime de façon permanente tous les projets de cet appareil et les remplacera avec les projets présents sur le serveur. Cette action ne peut pas être annulé.</string>
<string name="merge_projects_replace_confirm_button">Supprimer et remplacer</string>
<string name="settings_automatic_update_checks">Vérifier automatiquement les mises à jour</string>
<string name="settings_automatic_update_checks_hint">Recherche les nouvelles versions sur GitHub peu après le lancement et toutes les heures. Rien n'est téléchargé ou installé sans votre intervention.</string>
<string name="merge_projects_replace_confirm_title">Delete all local projects?</string>
<string name="merge_projects_replace_confirm_message">This permanently deletes every project on this device and replaces them with the projects from the server. This cannot be undone.</string>
<string name="merge_projects_replace_confirm_button">Delete and Replace</string>
<string name="settings_platform_settings_title">Paramètres de la plateforme</string>
<string name="settings_platform_settings_empty">Aucun paramètre spécifique à la plateforme.</string>
<string name="settings_keep_screen_on">Garder l'écran allumé</string>
@ -74,8 +71,8 @@
<string name="settings_spellcheck_notice">Si cela pose problème, désactivez-la simplement.</string>
<string name="settings_spellcheck_enable">Activer la correction orthographique</string>
<string name="settings_spellcheck_in_focus_enable">Activer en mode concentration</string>
<string name="settings_spellcheck_encyclopedia_enable">Apprendre les noms de l'encyclopédie</string>
<string name="settings_spellcheck_encyclopedia_enable_hint">Les noms d'entrés et les alias ne seront pas marqués comme mal orthographiés pendant que leur projet est ouvert</string>
<string name="settings_spellcheck_encyclopedia_enable">Learn encyclopedia names</string>
<string name="settings_spellcheck_encyclopedia_enable_hint">Entry names and aliases won&#8217;t be marked as misspelled while their project is open</string>
<string name="settings_spellcheck_dictionary">Dictionnaire</string>
<string name="settings_spellcheck_dictionary_android_info_title">Langue de correction orthographique</string>
<string name="settings_spellcheck_dictionary_android_info_message">Sur Android, la langue de correction orthographique est choisie par votre appareil, pas par l'application. Pour la modifier, changez les langues de votre système et de votre clavier dans les paramètres Android.</string>
@ -86,7 +83,6 @@
<string name="backup_manager_error_load_backups">Échec du chargement des sauvegardes : %1$s</string>
<string name="backup_manager_error_load_project_backups">Échec du chargement des sauvegardes du projet : %1$s</string>
<string name="backup_manager_error_delete_backup">Échec de la suppression de la sauvegarde : %1$s</string>
<string name="backup_manager_error_restore_backup">Échec de la suppression de la sauvegarde : %1$s</string>
<string name="backup_restore_dialog_title">Restaurer la sauvegarde</string>
<string name="backup_restore_dialog_message">Avertissement : la restauration de cette sauvegarde remplacera entièrement le contenu actuel du projet. Tous les fichiers existants seront supprimés et remplacés par les données de la sauvegarde. Cette action est irréversible.</string>
<string name="backup_restore_dialog_confirm">Restaurer</string>
@ -127,5 +123,5 @@
<string name="server_error_dns">Serveur introuvable. Vérifiez l'adresse du serveur et votre connexion Internet.</string>
<string name="server_error_connection_generic">Erreur réseau lors de la connexion à %1$s. Vérifiez votre connexion et réessayez.</string>
<string name="server_error_tls">Impossible d'établir une connection sécurisée à %1$s. Hammer se connecte uniquement via HTTPS, le serveur a besoin de son propre certificat TLS ou d'un proxy inverse qui lui en fournit un</string>
<string name="server_error_network_unavailable">Hammer ne peut pas établir une connexion réseau sur cet appareil, la requête ne peut donc pas atteindre le serveur. Un système de sécurité, un VPN ou un pare-feu peuvent être la source du problème.</string>
<string name="server_error_network_unavailable">Hammer could not open a network connection on this device, so the request never reached the server. Security software, a VPN, or a firewall may be blocking it.</string>
</resources>

View file

@ -30,7 +30,7 @@
<string name="encyclopedia_create_entry_cancel_button">Annuler</string>
<string name="encyclopedia_create_entry_toast_success">Entrée créée</string>
<string name="encyclopedia_create_entry_toast_too_long">Le nom de l'entrée est trop longue. Max %1$d</string>
<string name="encyclopedia_create_entry_toast_invalid_name">Le nom de l'entrée contient des caractères invalides</string>
<string name="encyclopedia_create_entry_toast_invalid_name">The entry name contains invalid characters</string>
<string name="encyclopedia_create_entry_toast_tag_too_long">L'Étiquette est trop longue. Max %1$d</string>
<string name="encyclopedia_create_entry_toast_alias_too_long">L'alias est trop long. Max %1$d</string>
<string name="encyclopedia_entry_aliases_label">Alias</string>

View file

@ -78,14 +78,10 @@
<string name="project_home_export_format_rtf">RTF (.rtf)</string>
<string name="project_home_export_cancel">Annuler</string>
<string name="project_home_export_execute">Exporter</string>
<string name="project_home_export_number_chapters_label">Nombre de titres de chapitre</string>
<string name="project_home_export_font_label">Police</string>
<string name="project_home_export_blank_lines_label">Conserver les lignes vides entre les paragraphes</string>
<string name="project_home_export_blank_lines_hint">Désactivez si vous séparez les paragraphes avec une ligne vide et ne voulez pas de paragraphes vides dans l'export. Les pauses de scène et les exécutions de deux ou plusieurs lignes vierges sont conservées.</string>
<string name="project_home_export_limit_scenes_label">Limiter aux scènes spécifiques</string>
<string name="project_home_export_limit_scenes_hint">Seules les scènes sélectionnées seront incluse dans l'export</string>
<string name="project_home_export_limit_scenes_label">Limit to specific scenes</string>
<string name="project_home_export_limit_scenes_hint">Only the selected scenes will be included in the export</string>
<string name="project_home_export_scenes_label">Scènes</string>
<string name="project_home_export_scenes_selected">%1$d sur %2$d sélectionnés</string>
<string name="project_home_export_scenes_selected">%1$d of %2$d selected</string>
<string name="project_home_export_scenes_select_all">Tout sélectionner</string>
<string name="project_home_export_scenes_clear_all">Tout effacer</string>
<string name="project_home_export_help_icon_description">En savoir plus sur les formats d'exportation</string>

View file

@ -7,7 +7,6 @@
<string name="projects_list_create_button">Créer un projet</string>
<string name="projects_list_toast_sync_complete">Projets synchronisés</string>
<string name="projects_list_toast_sync_failed">Échec de la synchronisation des projets</string>
<string name="projects_list_toast_delete_failed">Could not delete the project. Check that its folder is accessible and try again.</string>
<string name="projects_list_item_more_button">Plus</string>
<string name="projects_list_card_words">%1$s mots</string>
<string name="projects_list_card_by_author">par %1$s</string>

View file

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="reader_mode_section">Lecteur</string>
<string name="reader_mode_position">Chapitre %1$d sur %2$d</string>
<string name="reader_mode_edit">Modifier</string>
<string name="reader_mode_close">× Fermer</string>
<string name="reader_mode_decrease_font_size">Réduire la taille du texte</string>
<string name="reader_mode_increase_font_size">Augmenter la taille du texte</string>
<string name="reader_mode_reset_font_size">Réinitialiser la taille du texte</string>
<string name="reader_mode_toggle_scene_headings">Afficher les en-têtes de scène</string>
<string name="reader_mode_previous_chapter">← Chapitre précédent</string>
<string name="reader_mode_next_chapter">Chapitre suivant →</string>
<string name="reader_mode_empty_chapter">Ce chapitre n'a pas encore de scène</string>
<string name="reader_mode_empty_story">Il n'y a rien à lire pour le moment</string>
<string name="reader_mode_end">Fin de l'histoire</string>
</resources>

View file

@ -24,8 +24,7 @@
<string name="scene_editor_menu_item_archive">Archiver</string>
<string name="scene_editor_menu_item_save">Enregistrer</string>
<string name="scene_editor_menu_item_close">Fermer</string>
<string name="scene_editor_menu_item_read">Lire</string>
<string name="scene_editor_add_to_dictionary">Ajouter au dictionnaire</string>
<string name="scene_editor_add_to_dictionary">Add to dictionary</string>
<string name="archive_scene_dialog_title">Archiver la scène</string>
<string name="archive_scene_dialog_message">Voulez-vous vraiment archiver « %1$s » ? La scène sera masquée de la liste des scènes mais pourra être restaurée plus tard.</string>
<string name="archive_scene_dialog_confirm">Archiver</string>

View file

@ -16,8 +16,7 @@
<string name="create_sceneitem_dialog_create_button">Créer</string>
<string name="create_sceneitem_dialog_dismiss_button">Annuler</string>
<string name="scene_list_outline_overview_title">Plan de l'histoire</string>
<string name="scene_list_outline_button">Plan</string>
<string name="scene_list_reader_button">Lire</string>
<string name="scene_list_outline_overview_button">Aperçu du plan</string>
<string name="scene_list_outline_overview_none">&lt;aucun plan&gt;</string>
<string name="scene_list_outline_dismiss">Ignorer</string>
<string name="scene_list_outline_overview_eyebrow">Manuscrit</string>

View file

@ -52,11 +52,11 @@
<string name="project_settings_hero_no_author">par (aucun auteur défini)</string>
<string name="project_settings_spellcheck_section_title">Correction orthographique</string>
<string name="project_settings_spellcheck_mismatch_caption">La vérification orthographique est désactivée : la langue du projet (%1$s) ne correspond pas à votre dictionnaire (%2$s). Changez là pour la réactiver.</string>
<string name="project_settings_spellcheck_encyclopedia_enable">Apprendre les noms du dictionnaire dans ce projet</string>
<string name="project_settings_spellcheck_encyclopedia_enable_hint">Désactivez pour garder les noms d'entrés en dehors du dictionnaire de vérification orthographique</string>
<string name="project_settings_dictionary_label">Dictionnaire du projet</string>
<string name="project_settings_spellcheck_encyclopedia_enable">Learn encyclopedia names in this project</string>
<string name="project_settings_spellcheck_encyclopedia_enable_hint">Turn off to keep this project&#8217;s entry names out of the spell check dictionary</string>
<string name="project_settings_dictionary_label">Project dictionary</string>
<string name="project_settings_dictionary_hint">↵ ajouter</string>
<string name="project_settings_dictionary_placeholder">Les mots que le correcteur orthographique devrait accepter dans ce projet</string>
<string name="project_settings_dictionary_placeholder">Words the spell checker should accept in this project</string>
<string name="project_settings_folio_caption">Paramètres du projet · %1$s</string>
<string name="project_settings_folio_section_count">§§ %1$d</string>
<string name="sync_conflict_project_data_title">Conflit de paramètres du projet</string>
@ -66,13 +66,13 @@
<string name="sync_conflict_project_data_field_word_goal">Objectif de nombre de mots</string>
<string name="sync_conflict_project_data_field_tags">Étiquettes</string>
<string name="sync_conflict_project_data_field_language">Langue</string>
<string name="sync_conflict_project_data_field_dictionary">Apprendre les noms de l'encyclopédie</string>
<string name="sync_conflict_project_data_field_dictionary">Learn Encyclopedia Names</string>
<string name="sync_conflict_project_data_value_unset">(non défini)</string>
<string name="sync_conflict_project_data_value_on">Activer</string>
<string name="sync_conflict_project_data_value_off">Désactiver</string>
<string name="sync_conflict_project_data_value_on">On</string>
<string name="sync_conflict_project_data_value_off">Off</string>
<string name="sync_conflict_project_data_cadence_day">jour</string>
<string name="sync_conflict_project_data_cadence_week">semaine</string>
<string name="sync_conflict_project_data_dictionary_merged">Les mots du dictionnaire des deux parties sont conservés.</string>
<string name="sync_conflict_project_data_dictionary_merged">Project dictionary words from both sides are kept.</string>
<string name="sync_conflict_project_data_resolve_button">Résoudre</string>
<string name="sync_conflict_encyclopedia_label_name">Nom :</string>
<string name="sync_conflict_encyclopedia_label_type">Type :</string>
@ -116,16 +116,16 @@
<string name="sync_log_account_project_rename_client_from_server_failure">Échec du renommage du projet depuis le serveur : %1$s</string>
<string name="sync_log_account_project_create_server_success">Projet créé sur le serveur : %1$s</string>
<string name="sync_log_account_project_create_server_failure">Échec de la création du projet sur le serveur : %1$s</string>
<string name="sync_log_account_project_id_save_failure">Projet a été créé sur le serveur, mais n'a pas pu sauvegarder son ID localement : %1$s</string>
<string name="sync_log_account_project_id_save_failure">Project created on server, but failed to save its ID locally: %1$s</string>
<string name="sync_log_account_project_create_client">Projet local créé : %1$s</string>
<string name="sync_log_account_project_create_client_local">Définition de l'ID serveur du projet local : %1$s</string>
<string name="sync_log_account_project_create_local_failure">Impossible de créer un projet local depuis le serveur : %1$s</string>
<string name="sync_log_account_project_create_client_name_taken">Projet du serveur %1$s ignoré : un projet local possédant ce nom est déjà synchronisé depuis un autre serveur</string>
<string name="sync_log_account_project_create_local_failure">Failed to create local project from server: %1$s</string>
<string name="sync_log_account_project_create_client_name_taken">Skipped server project %1$s: a local project of that name already syncs with a different server project</string>
<string name="sync_log_begin_account">Synchronisation du compte…</string>
<string name="sync_log_begin_projects">Synchronisation des projets…</string>
<string name="sync_log_end_projects">Synchronisation des projets terminée</string>
<string name="sync_log_begin_project">Synchronisation du projet : %1$s</string>
<string name="sync_log_project_unchanged">Pas de modification, ignorée</string>
<string name="sync_log_project_unchanged">No changes, skipped</string>
<string name="sync_log_project_failed">Échec de la synchronisation : %1$s</string>
<string name="sync_log_project_conflict">Il y a un conflit dans le projet : %1$s. Ouvrez ce projet et synchronisez pour le résoudre.</string>
<string name="sync_log_client_data_computed">Données d'entité client calculées</string>
@ -193,7 +193,8 @@
<string name="server_setup_help_patreon_link">Patreon</string>
<string name="server_setup_help_hammer_link">Hammer.Ink</string>
<string name="server_setup_help_registration_header">Inscription</string>
<string name="server_setup_help_registration_body">Les serveurs ont fermé leur inscription, ce qui signifie que l'administrateur du serveur doit vous ajouter à la liste des utilisateurs autorisés avant que vous ne puissiez créer un compte.</string>
<string name="server_setup_help_registration_body">Servers have closed registration, meaning the server admin needs to add you to the server's allowed users list before you can create an account.
</string>
<string name="server_setup_help_setup_header">Configuration</string>
<string name="server_setup_help_setup_step1">Saisissez l'URL du serveur, par exemple « hammer.ink » pour le serveur officiel.</string>
<string name="server_setup_help_setup_step2">Saisissez votre e-mail et votre mot de passe, puis appuyez sur « Créer » pour créer votre compte.</string>

View file

@ -22,7 +22,6 @@
<string name="confirm_dialog_negative">No</string>
<string name="expand">Espandi</string>
<string name="collapse">Comprimi</string>
<string name="markdown_format_bar_text_size">Text size</string>
<string name="markdown_format_bar_decrease_text_size">Riduci la dimensione del testo</string>
<string name="markdown_format_bar_increase_text_size">Aumenta la dimensione del testo</string>
<string name="markdown_format_bar_reset_text_size">Reimposta la dimensione del testo</string>

View file

@ -2,7 +2,7 @@
<resources>
<string name="about_description">Uno strumento semplice per costruire storie.</string>
<string name="about_studio_header">Studio</string>
<string name="about_community_studio_attribution">Hammer è realizzato da Dark Rock Studios, insieme a una famiglia di altre app open source che rispettano la privacy.</string>
<string name="about_community_studio_attribution">Hammer is made by Dark Rock Studios, alongside a family of other privacy-respecting, open source apps.</string>
<string name="about_community_header">Community</string>
<string name="about_community_discord_link">Discord</string>
<string name="about_community_reddit_link">Reddit</string>
@ -10,20 +10,9 @@
<string name="about_attribution_header">Attribuzione</string>
<string name="about_attribution_libraries_button">Librerie</string>
<string name="about_attribution_studio_button">Dark Rock Studios</string>
<string name="about_version_changes_button">Modifiche</string>
<string name="about_version_github_button">Rilascio su GitHub</string>
<string name="about_update_check_button">Check for Updates</string>
<string name="about_update_checking">Checking for updates&#8230;</string>
<string name="about_update_up_to_date">Hammer is up to date.</string>
<string name="about_update_available">Hammer %1$s is available.</string>
<string name="about_update_install_button">Update to %1$s</string>
<string name="about_update_downloading">Downloading Hammer %1$s (%2$s)</string>
<string name="about_update_installing">Installing Hammer %1$s. Hammer will restart.</string>
<string name="about_update_failed">Could not update to %1$s: %2$s</string>
<string name="about_update_retry_button">Retry</string>
<string name="about_update_later_button">Later</string>
<string name="about_update_quit_notice">Hammer will quit to install the update, then reopen.</string>
<string name="changelog_dialog_title">Novità</string>
<string name="about_version_changes_button">Changes</string>
<string name="about_version_github_button">GitHub Release</string>
<string name="changelog_dialog_title">What's New</string>
<string name="update_dialog_release_notes_section">Note di rilascio</string>
<string name="update_dialog_release_notes_empty">Nessuna nota di rilascio fornita.</string>
<string name="update_dialog_open_release_button">Apri la versione</string>
@ -32,9 +21,9 @@
<string name="about_logs_export_button">Esporta i log</string>
<string name="about_logs_no_logs">Nessun file di log trovato</string>
<string name="about_logs_open_tooltip">Apri la cartella dei log</string>
<string name="about_logs_export_success">Log esportati.</string>
<string name="about_logs_export_failed">Impossibile esportare i log. Controlla la cartella dei log.</string>
<string name="about_logs_copy_diagnostics_button">Copia diagnostica</string>
<string name="about_logs_copy_diagnostics_success">Diagnostica copiata negli appunti.</string>
<string name="about_logs_copy_diagnostics_failed">Impossibile copiare la diagnostica.</string>
<string name="about_logs_export_success">Logs exported.</string>
<string name="about_logs_export_failed">Could not export the logs. See the log directory.</string>
<string name="about_logs_copy_diagnostics_button">Copy Diagnostics</string>
<string name="about_logs_copy_diagnostics_success">Diagnostics copied to the clipboard.</string>
<string name="about_logs_copy_diagnostics_failed">Could not copy the diagnostics.</string>
</resources>

View file

@ -9,7 +9,6 @@
<string name="settings_theme_label">Tema dell’interfaccia</string>
<string name="settings_theme_light">CHIARO</string>
<string name="settings_theme_dark">SCURO</string>
<string name="settings_theme_oled_black">OLED</string>
<string name="settings_theme_followsystem">SISTEMA</string>
<string name="settings_initial_screen_label">Schermata iniziale del progetto</string>
<string name="settings_initial_screen_home">Home</string>
@ -56,8 +55,6 @@
<string name="merge_projects_replace_confirm_title">Delete all local projects?</string>
<string name="merge_projects_replace_confirm_message">This permanently deletes every project on this device and replaces them with the projects from the server. This cannot be undone.</string>
<string name="merge_projects_replace_confirm_button">Delete and Replace</string>
<string name="settings_automatic_update_checks">Check for updates automatically</string>
<string name="settings_automatic_update_checks_hint">Looks for a new release on GitHub shortly after launch and every few hours. Nothing is downloaded or installed until you choose to update.</string>
<string name="settings_platform_settings_title">Impostazioni della piattaforma</string>
<string name="settings_platform_settings_empty">Nessuna impostazione specifica della piattaforma.</string>
<string name="settings_keep_screen_on">Tieni lo schermo acceso</string>
@ -86,7 +83,6 @@
<string name="backup_manager_error_load_backups">Impossibile caricare i backup: %1$s</string>
<string name="backup_manager_error_load_project_backups">Impossibile caricare i backup del progetto: %1$s</string>
<string name="backup_manager_error_delete_backup">Impossibile eliminare il backup: %1$s</string>
<string name="backup_manager_error_restore_backup">Failed to restore backup: %1$s</string>
<string name="backup_restore_dialog_title">Ripristina backup</string>
<string name="backup_restore_dialog_message">Attenzione: ripristinare questo backup sostituirà completamente il contenuto attuale del progetto. Tutti i file esistenti verranno eliminati e sostituiti con i dati del backup. Questa azione non può essere annullata.</string>
<string name="backup_restore_dialog_confirm">Ripristina</string>

View file

@ -78,10 +78,6 @@
<string name="project_home_export_format_rtf">RTF (.rtf)</string>
<string name="project_home_export_cancel">Annulla</string>
<string name="project_home_export_execute">Esporta</string>
<string name="project_home_export_number_chapters_label">Number chapter titles</string>
<string name="project_home_export_font_label">Font</string>
<string name="project_home_export_blank_lines_label">Keep blank lines between paragraphs</string>
<string name="project_home_export_blank_lines_hint">Turn off if you separate paragraphs with a blank line and do not want empty paragraphs in the export. Scene breaks and runs of two or more blank lines are kept.</string>
<string name="project_home_export_limit_scenes_label">Limit to specific scenes</string>
<string name="project_home_export_limit_scenes_hint">Only the selected scenes will be included in the export</string>
<string name="project_home_export_scenes_label">Scene</string>
@ -119,7 +115,7 @@
<string name="project_home_import_large_scene_label">Large scene</string>
<string name="project_home_import_large_scene_one">One scene holds %1$s words, so chapter detection probably missed your chapter titles. Try another detection setting.</string>
<string name="project_home_import_large_scene_many">%1$d scenes hold %2$s words or more, so chapter detection probably missed some chapter titles. Try another detection setting.</string>
<string name="project_home_import_scene_word_count">%1$s P</string>
<string name="project_home_import_scene_word_count">%1$s W</string>
<string name="project_home_import_cancel">Annulla</string>
<string name="project_home_import_execute">Importa</string>
<string name="project_home_action_settings_button">Impostazioni</string>

View file

@ -7,7 +7,7 @@
<string name="project_nav_time_line">Cronologia</string>
<string name="project_nav_home_short">HOME</string>
<string name="project_nav_scene_editor_short">STOR</string>
<string name="project_nav_notes_short">NOTA</string>
<string name="project_nav_notes_short">NOTE</string>
<string name="project_nav_encyclopedia_short">ENCI</string>
<string name="project_nav_time_line_short">CRON</string>
<string name="nav_rail_expand">Espandi la navigazione</string>

View file

@ -7,7 +7,6 @@
<string name="projects_list_create_button">Crea progetto</string>
<string name="projects_list_toast_sync_complete">Progetti sincronizzati</string>
<string name="projects_list_toast_sync_failed">Impossibile sincronizzare i progetti</string>
<string name="projects_list_toast_delete_failed">Could not delete the project. Check that its folder is accessible and try again.</string>
<string name="projects_list_item_more_button">Altro</string>
<string name="projects_list_card_words">%1$s parole</string>
<string name="projects_list_card_by_author">di %1$s</string>

View file

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="reader_mode_section">Reader</string>
<string name="reader_mode_position">Chapter %1$d of %2$d</string>
<string name="reader_mode_edit">Edit ↗</string>
<string name="reader_mode_close">× Close</string>
<string name="reader_mode_decrease_font_size">Riduci la dimensione del testo</string>
<string name="reader_mode_increase_font_size">Aumenta la dimensione del testo</string>
<string name="reader_mode_reset_font_size">Reimposta la dimensione del testo</string>
<string name="reader_mode_toggle_scene_headings">Show scene headings</string>
<string name="reader_mode_previous_chapter">← Previous chapter</string>
<string name="reader_mode_next_chapter">Next chapter →</string>
<string name="reader_mode_empty_chapter">This chapter has no scenes yet</string>
<string name="reader_mode_empty_story">There is nothing to read yet</string>
<string name="reader_mode_end">End of story</string>
</resources>

View file

@ -24,7 +24,6 @@
<string name="scene_editor_menu_item_archive">Archivia</string>
<string name="scene_editor_menu_item_save">Salva</string>
<string name="scene_editor_menu_item_close">Chiudi</string>
<string name="scene_editor_menu_item_read">Read</string>
<string name="scene_editor_add_to_dictionary">Add to dictionary</string>
<string name="archive_scene_dialog_title">Archivia scena</string>
<string name="archive_scene_dialog_message">Vuoi davvero archiviare «%1$s»? La scena verrà nascosta dall’elenco delle scene ma potrà essere ripristinata in seguito.</string>

View file

@ -16,8 +16,7 @@
<string name="create_sceneitem_dialog_create_button">Crea</string>
<string name="create_sceneitem_dialog_dismiss_button">Annulla</string>
<string name="scene_list_outline_overview_title">Scaletta della storia</string>
<string name="scene_list_outline_button">Scaletta</string>
<string name="scene_list_reader_button">Read</string>
<string name="scene_list_outline_overview_button">Panoramica della scaletta</string>
<string name="scene_list_outline_overview_none">&lt;nessuna scaletta&gt;</string>
<string name="scene_list_outline_dismiss">Ignora</string>
<string name="scene_list_outline_overview_eyebrow">Manoscritto</string>

View file

@ -22,7 +22,6 @@
<string name="confirm_dialog_negative">Não</string>
<string name="expand">Expandir</string>
<string name="collapse">Encolher</string>
<string name="markdown_format_bar_text_size">Text size</string>
<string name="markdown_format_bar_decrease_text_size">Diminuir o tamanho do texto</string>
<string name="markdown_format_bar_increase_text_size">Aumentar o tamanho do texto</string>
<string name="markdown_format_bar_reset_text_size">Redefinir o tamanho do texto</string>

View file

@ -12,17 +12,6 @@
<string name="about_attribution_studio_button">Dark Rock Studios</string>
<string name="about_version_changes_button">Changes</string>
<string name="about_version_github_button">GitHub Release</string>
<string name="about_update_check_button">Check for Updates</string>
<string name="about_update_checking">Checking for updates&#8230;</string>
<string name="about_update_up_to_date">Hammer is up to date.</string>
<string name="about_update_available">Hammer %1$s is available.</string>
<string name="about_update_install_button">Update to %1$s</string>
<string name="about_update_downloading">Downloading Hammer %1$s (%2$s)</string>
<string name="about_update_installing">Installing Hammer %1$s. Hammer will restart.</string>
<string name="about_update_failed">Could not update to %1$s: %2$s</string>
<string name="about_update_retry_button">Retry</string>
<string name="about_update_later_button">Later</string>
<string name="about_update_quit_notice">Hammer will quit to install the update, then reopen.</string>
<string name="changelog_dialog_title">What's New</string>
<string name="update_dialog_release_notes_section">Notas de versão</string>
<string name="update_dialog_release_notes_empty">Nenhuma nota de versão fornecida.</string>

View file

@ -9,7 +9,6 @@
<string name="settings_theme_label">Tema de 'interface' do usuário</string>
<string name="settings_theme_light">CLARO</string>
<string name="settings_theme_dark">ESCURO</string>
<string name="settings_theme_oled_black">OLED</string>
<string name="settings_theme_followsystem">SISTEMA</string>
<string name="settings_initial_screen_label">Tela Inicial do Projeto</string>
<string name="settings_initial_screen_home">Início</string>
@ -56,8 +55,6 @@
<string name="merge_projects_replace_confirm_title">Delete all local projects?</string>
<string name="merge_projects_replace_confirm_message">This permanently deletes every project on this device and replaces them with the projects from the server. This cannot be undone.</string>
<string name="merge_projects_replace_confirm_button">Delete and Replace</string>
<string name="settings_automatic_update_checks">Check for updates automatically</string>
<string name="settings_automatic_update_checks_hint">Looks for a new release on GitHub shortly after launch and every few hours. Nothing is downloaded or installed until you choose to update.</string>
<string name="settings_platform_settings_title">Configurações da Plataforma</string>
<string name="settings_platform_settings_empty">Nenhuma configuração específica da plataforma.</string>
<string name="settings_keep_screen_on">Manter a tela ligada</string>
@ -86,7 +83,6 @@
<string name="backup_manager_error_load_backups">Falha ao carregar 'backups': %1$s</string>
<string name="backup_manager_error_load_project_backups">Falha ao carregar 'backups' para o projeto: %1$s</string>
<string name="backup_manager_error_delete_backup">Falha ao excluir 'backup': %1$s</string>
<string name="backup_manager_error_restore_backup">Failed to restore backup: %1$s</string>
<string name="backup_restore_dialog_title">Restaurar 'Backup'</string>
<string name="backup_restore_dialog_message">Aviso: Restaurar este 'backup' substituirá completamente o conteúdo atual do projeto. Todos os arquivos existentes serão apagados e substituídos pelos dados de 'backup'. Esta ação não pode ser desfeita.</string>
<string name="backup_restore_dialog_confirm">Restaurar</string>

View file

@ -78,10 +78,6 @@
<string name="project_home_export_format_rtf">RTF (.rtf)</string>
<string name="project_home_export_cancel">Cancelar</string>
<string name="project_home_export_execute">Exportar</string>
<string name="project_home_export_number_chapters_label">Number chapter titles</string>
<string name="project_home_export_font_label">Font</string>
<string name="project_home_export_blank_lines_label">Keep blank lines between paragraphs</string>
<string name="project_home_export_blank_lines_hint">Turn off if you separate paragraphs with a blank line and do not want empty paragraphs in the export. Scene breaks and runs of two or more blank lines are kept.</string>
<string name="project_home_export_limit_scenes_label">Limit to specific scenes</string>
<string name="project_home_export_limit_scenes_hint">Only the selected scenes will be included in the export</string>
<string name="project_home_export_scenes_label">Cenas</string>

View file

@ -7,7 +7,6 @@
<string name="projects_list_create_button">Criar Projeto</string>
<string name="projects_list_toast_sync_complete">Projetos sincronizados</string>
<string name="projects_list_toast_sync_failed">Falha ao sincronizar projetos</string>
<string name="projects_list_toast_delete_failed">Could not delete the project. Check that its folder is accessible and try again.</string>
<string name="projects_list_item_more_button">Mais</string>
<string name="projects_list_card_words">%1$s palavras</string>
<string name="projects_list_card_by_author">por %1$s</string>

View file

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="reader_mode_section">Reader</string>
<string name="reader_mode_position">Chapter %1$d of %2$d</string>
<string name="reader_mode_edit">Edit ↗</string>
<string name="reader_mode_close">× Close</string>
<string name="reader_mode_decrease_font_size">Diminuir o tamanho do texto</string>
<string name="reader_mode_increase_font_size">Aumentar o tamanho do texto</string>
<string name="reader_mode_reset_font_size">Redefinir o tamanho do texto</string>
<string name="reader_mode_toggle_scene_headings">Show scene headings</string>
<string name="reader_mode_previous_chapter">← Previous chapter</string>
<string name="reader_mode_next_chapter">Next chapter →</string>
<string name="reader_mode_empty_chapter">This chapter has no scenes yet</string>
<string name="reader_mode_empty_story">There is nothing to read yet</string>
<string name="reader_mode_end">End of story</string>
</resources>

View file

@ -24,7 +24,6 @@
<string name="scene_editor_menu_item_archive">Arquivar</string>
<string name="scene_editor_menu_item_save">Salvar</string>
<string name="scene_editor_menu_item_close">Fechar</string>
<string name="scene_editor_menu_item_read">Read</string>
<string name="scene_editor_add_to_dictionary">Add to dictionary</string>
<string name="archive_scene_dialog_title">Arquivar Cena</string>
<string name="archive_scene_dialog_message">Tem certeza que deseja arquivar '%1$s'? A cena será ocultada da lista de cenas, mas pode ser restaurada mais tarde.</string>

View file

@ -16,8 +16,7 @@
<string name="create_sceneitem_dialog_create_button">Criar</string>
<string name="create_sceneitem_dialog_dismiss_button">Cancelar</string>
<string name="scene_list_outline_overview_title">Esboço da história</string>
<string name="scene_list_outline_button">Esboço</string>
<string name="scene_list_reader_button">Read</string>
<string name="scene_list_outline_overview_button">Visão geral</string>
<string name="scene_list_outline_overview_none">&lt;sem esboço&gt;</string>
<string name="scene_list_outline_dismiss">Descartar</string>
<string name="scene_list_outline_overview_eyebrow">Manuscrito</string>

View file

@ -1,41 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Hammer</string>
<string name="save_all_toast">Saved all Scenes</string>
<string name="unsaved_scenes_dialog_title">Unsaved Scenes</string>
<string name="unsaved_scenes_dialog_message">What should be done with unsaved scenes?</string>
<string name="unsaved_encyclopedia_dialog_title">Unsaved Encyclopedia Entry</string>
<string name="unsaved_encyclopedia_dialog_message">Discard entry?</string>
<string name="unsaved_notes_dialog_title">Unsaved Note</string>
<string name="unsaved_notes_dialog_message">Discard note?</string>
<string name="unsaved_timeline_dialog_title">Unsaved Timeline Event</string>
<string name="unsaved_timeline_dialog_message">Discard event?</string>
<string name="unsaved_dialog_negative_button">Cancel</string>
<string name="unsaved_dialog_positive_button">Discard and close</string>
<string name="unsaved_entity_dialog_negative_button">Discard</string>
<string name="unsaved_entity_dialog_neutral_button">Cancel</string>
<string name="unsaved_entity_dialog_positive_button">Save</string>
<string name="close_dialog_button">Close</string>
<string name="more_menu_button">More</string>
<string name="network_request_failure_parse_body">Failed to parse response body: %1$s</string>
<string name="confirm_dialog_positive">Yes</string>
<string name="confirm_dialog_negative">No</string>
<string name="expand">Expand</string>
<string name="collapse">Collapse</string>
<string name="markdown_format_bar_text_size">Text size</string>
<string name="markdown_format_bar_decrease_text_size">Decrease text size</string>
<string name="markdown_format_bar_increase_text_size">Increase text size</string>
<string name="markdown_format_bar_reset_text_size">Reset text size</string>
<string name="markdown_format_bar_find_replace">Find and replace</string>
<string name="markdown_format_bar_bold">Bold</string>
<string name="markdown_format_bar_italic">Italic</string>
<string name="markdown_format_bar_strikethrough">Strikethrough</string>
<string name="markdown_format_bar_heading">Heading</string>
<string name="markdown_format_bar_blockquote">Quote</string>
<string name="markdown_format_bar_bullet_list">Bulleted list</string>
<string name="markdown_format_bar_numbered_list">Numbered list</string>
<string name="markdown_format_bar_horizontal_rule">Horizontal rule</string>
<string name="markdown_format_bar_undo">Undo</string>
<string name="markdown_format_bar_redo">Redo</string>
<string name="form_dialog_keyboard_hint">ESC cancel</string>
</resources>

View file

@ -1,40 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="about_description">A simple tool for building stories.</string>
<string name="about_studio_header">Studio</string>
<string name="about_community_studio_attribution">Hammer is made by Dark Rock Studios, alongside a family of other privacy-respecting, open source apps.</string>
<string name="about_community_header">Community</string>
<string name="about_community_discord_link">Discord</string>
<string name="about_community_reddit_link">Reddit</string>
<string name="about_community_github_link">Github</string>
<string name="about_attribution_header">Attribution</string>
<string name="about_attribution_libraries_button">Libraries</string>
<string name="about_attribution_studio_button">Dark Rock Studios</string>
<string name="about_version_changes_button">Changes</string>
<string name="about_version_github_button">GitHub Release</string>
<string name="about_update_check_button">Check for Updates</string>
<string name="about_update_checking">Checking for updates&#8230;</string>
<string name="about_update_up_to_date">Hammer is up to date.</string>
<string name="about_update_available">Hammer %1$s is available.</string>
<string name="about_update_install_button">Update to %1$s</string>
<string name="about_update_downloading">Downloading Hammer %1$s (%2$s)</string>
<string name="about_update_installing">Installing Hammer %1$s. Hammer will restart.</string>
<string name="about_update_failed">Could not update to %1$s: %2$s</string>
<string name="about_update_retry_button">Retry</string>
<string name="about_update_later_button">Later</string>
<string name="about_update_quit_notice">Hammer will quit to install the update, then reopen.</string>
<string name="changelog_dialog_title">What's New</string>
<string name="update_dialog_release_notes_section">Release notes</string>
<string name="update_dialog_release_notes_empty">No release notes provided.</string>
<string name="update_dialog_open_release_button">Open Release</string>
<string name="about_logs_header">Application Logs</string>
<string name="about_logs_directory_label">Log Directory:</string>
<string name="about_logs_export_button">Export Logs</string>
<string name="about_logs_no_logs">No log files found</string>
<string name="about_logs_open_tooltip">Open log directory</string>
<string name="about_logs_export_success">Logs exported.</string>
<string name="about_logs_export_failed">Could not export the logs. See the log directory.</string>
<string name="about_logs_copy_diagnostics_button">Copy Diagnostics</string>
<string name="about_logs_copy_diagnostics_success">Diagnostics copied to the clipboard.</string>
<string name="about_logs_copy_diagnostics_failed">Could not copy the diagnostics.</string>
</resources>

View file

@ -1,146 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_settings_breadcrumb">ACCOUNT</string>
<string name="account_settings_subtitle_local_install">LOCAL INSTALL</string>
<string name="account_settings_folio_caption">ACCOUNT SETTINGS</string>
<string name="settings_backups_header">Backups</string>
<string name="settings_backups_explainations">This backup system is meant to protect against data loss and corruption. It is NOT intended as a way to keep older revisions of your stories. Please use the Drafts system for that.
</string>
<string name="settings_backups_manage_button">Manage Backups</string>
<string name="settings_theme_label">UI Theme</string>
<string name="settings_theme_light">LIGHT</string>
<string name="settings_theme_dark">DARK</string>
<string name="settings_theme_oled_black">OLED</string>
<string name="settings_theme_followsystem">SYSTEM</string>
<string name="settings_initial_screen_label">Initial Project Screen</string>
<string name="settings_initial_screen_home">Home</string>
<string name="settings_initial_screen_editor">Scene Editor</string>
<string name="settings_projects_directory">Projects Directory</string>
<string name="settings_projects_directory_description">Set the directory Hammer will use to store project data in.
</string>
<string name="settings_projects_directory_button">Select Dir</string>
<string name="settings_example_project_header">Example Project</string>
<string name="settings_example_project_description">Install an example project to see how different features are used.
</string>
<string name="settings_example_project_button">Reinstall</string>
<string name="settings_example_project_success_message">Example project created.</string>
<string name="settings_server_status_connected">CONNECTED</string>
<string name="settings_server_status_not_connected">NOT CONNECTED</string>
<string name="settings_server_url_greeble">URL</string>
<string name="settings_server_help_link">HELP ↗</string>
<string name="settings_server_description">Configure data syncing</string>
<string name="settings_server_setup_button">Set Up</string>
<string name="settings_server_auto_sync">Auto-Sync</string>
<string name="settings_server_sync_backup">Backup on sync</string>
<string name="settings_server_sync_auto_close">Close Sync Dialogs on Success</string>
<string name="settings_server_max_backups">Max Backups per Project</string>
<string name="settings_server_max_backups_error">Must be between 1 and %1$d</string>
<string name="settings_server_remove_server_button">Remove Server</string>
<string name="settings_remove_server_dialog_title">Remove Server</string>
<string name="settings_remove_server_dialog_message">Are you sure you want to remove the server?</string>
<string name="settings_server_setup_title">Set Up Server</string>
<string name="settings_server_setup_url_hint">Server URL: hammer.ink</string>
<string name="settings_server_setup_insecure_warning">Plain HTTP: your email, password and writing travel unencrypted. Only use this on a network you trust.</string>
<string name="settings_server_setup_email_hint">E-Mail: user@example.com</string>
<string name="settings_server_setup_password_hint">Password</string>
<string name="settings_server_setup_password_requirements">%1$d-%2$d characters</string>
<string name="settings_server_setup_password_show">Show password</string>
<string name="settings_server_setup_password_hide">Hide password</string>
<string name="settings_server_setup_login_button">Log In</string>
<string name="settings_server_setup_create_button">Create</string>
<string name="settings_server_setup_cancel_button">Cancel</string>
<string name="merge_projects_dialog_title">Merge existing projects with the server?</string>
<string name="merge_projects_mode_label">PROJECT DATA</string>
<string name="merge_projects_mode_merge">Merge</string>
<string name="merge_projects_mode_replace">Replace</string>
<string name="merge_projects_explain_merge">Hammer will match your local projects to projects already on the server by name. Projects that only exist on the server will be downloaded, and projects that only exist on this device will be uploaded.</string>
<string name="merge_projects_explain_replace">All of your local projects will be deleted and replaced with whatever is on the server.</string>
<string name="merge_projects_continue_button">Continue</string>
<string name="merge_projects_replace_confirm_title">Delete all local projects?</string>
<string name="merge_projects_replace_confirm_message">This permanently deletes every project on this device and replaces them with the projects from the server. This cannot be undone.</string>
<string name="merge_projects_replace_confirm_button">Delete and Replace</string>
<string name="settings_automatic_update_checks">Check for updates automatically</string>
<string name="settings_automatic_update_checks_hint">Looks for a new release on GitHub shortly after launch and every few hours. Nothing is downloaded or installed until you choose to update.</string>
<string name="settings_platform_settings_title">Platform Settings</string>
<string name="settings_platform_settings_empty">No platform-specific settings.</string>
<string name="settings_keep_screen_on">Keep screen on</string>
<string name="settings_enable_dnd_focus_mode_title">Enable Do Not Disturb when in Focus Mode</string>
<string name="settings_dnd_permission_button">Grant DND Permission</string>
<string name="settings_dnd_permission_explanation">Hammer needs permission to enable Do Not Disturb mode when you are in Focus Mode.
</string>
<string name="settings_storage_public_label">Store projects in public storage</string>
<string name="settings_storage_public_hint">Keep your projects in the shared Documents folder so other apps and your file manager can reach them.
</string>
<string name="settings_storage_location_label">Projects Location</string>
<string name="settings_storage_permission_explanation">Hammer needs file access permission to store projects in public storage.
</string>
<string name="settings_storage_permission_button">Grant File Access</string>
<string name="notice_experimental_label">Experimental</string>
<string name="settings_spellcheck_heading">Spell Checking</string>
<string name="settings_spellcheck_notice">If it is causing problems just turn it off.</string>
<string name="settings_spellcheck_enable">Enable spell checking</string>
<string name="settings_spellcheck_in_focus_enable">Enable in focus mode</string>
<string name="settings_spellcheck_encyclopedia_enable">Learn encyclopedia names</string>
<string name="settings_spellcheck_encyclopedia_enable_hint">Entry names and aliases won&#8217;t be marked as misspelled while their project is open</string>
<string name="settings_spellcheck_dictionary">Dictionary</string>
<string name="settings_spellcheck_dictionary_android_info_title">Spell check language</string>
<string name="settings_spellcheck_dictionary_android_info_message">On Android, the spell check language is chosen by your device, not the app. To change it, update your system and keyboard languages in Android Settings.</string>
<string name="settings_server_enable_sync_backup_desc">Set up a server to sync your projects across devices and create secure backups.
</string>
<string name="settings_server_sync_backup_preferences">Sync &amp; Backup Preferences</string>
<string name="settings_server_danger_zone">Danger Zone</string>
<!-- Backup Manager Dialog -->
<string name="backup_manager_error_load_backups">Failed to load backups: %1$s</string>
<string name="backup_manager_error_load_project_backups">Failed to load backups for project: %1$s</string>
<string name="backup_manager_error_delete_backup">Failed to delete backup: %1$s</string>
<string name="backup_manager_error_restore_backup">Failed to restore backup: %1$s</string>
<string name="backup_restore_dialog_title">Restore Backup</string>
<string name="backup_restore_dialog_message">Warning: Restoring this backup will completely replace the current contents of the project. All existing files will be deleted and replaced with the backup data. This action cannot be undone.
</string>
<string name="backup_restore_dialog_confirm">Restore</string>
<string name="backup_restore_dialog_cancel">Cancel</string>
<string name="backup_manager_title">Manage Backups</string>
<string name="backup_manager_no_backups">No backups found</string>
<string name="backup_manager_select_project_hint">Select a project</string>
<string name="backup_manager_no_backups_for_project">No backups for this project</string>
<string name="backup_manager_restore_button">Restore</string>
<string name="backup_manager_delete_button">Delete backup</string>
<string name="backup_manager_show_button">Show in Folder</string>
<string name="backup_manager_export_button">Export</string>
<string name="account_sync_confirm_cancel_title">Cancel Sync?</string>
<string name="account_sync_confirm_cancel_message">Are you sure you want to cancel the sync?</string>
<string name="account_sync_toast_canceled">Sync canceled</string>
<!-- Server Setup Validation Errors -->
<string name="server_setup_error_unknown">An unexpected error occurred. Please try again.</string>
<string name="server_setup_error_invalid_url">Invalid server address. Enter a URL like "hammer.ink" or an IP address.
</string>
<string name="server_setup_error_invalid_email">Please enter a valid email address.</string>
<string name="server_setup_error_password_too_short">Password must be at least 8 characters.</string>
<string name="server_setup_error_password_too_long">Password must be 64 characters or less.</string>
<string name="server_setup_error_password_no_uppercase">Password must contain at least one uppercase letter.
</string>
<string name="server_setup_error_password_no_lowercase">Password must contain at least one lowercase letter.
</string>
<string name="server_setup_error_password_no_number">Password must contain at least one number.</string>
<string name="server_setup_error_password_no_special">Password must contain at least one special character.</string>
<!-- Server Setup Toast Messages -->
<string name="settings_server_setup_toast_success">Server setup successful!</string>
<string name="settings_server_setup_toast_failure">Server setup failed: %1$s</string>
<string name="settings_server_setup_toast_failure_unknown">Server setup failed. Please check your connection and try again.
</string>
<!-- Terms of Service -->
<string name="settings_server_tos_title">Terms of Service</string>
<string name="settings_server_tos_accept">Accept</string>
<string name="settings_server_tos_decline">Decline</string>
<string name="settings_server_tos_declined">You must accept the Terms of Service to create an account.</string>
<!-- Network Errors -->
<string name="server_error_connection_timeout">Could not connect to server. Make sure the server is running and the address is correct.
</string>
<string name="server_error_timeout">The server is not responding. Check your internet connection and try again.
</string>
<string name="server_error_dns">Server not found. Check the server address and your internet connection.</string>
<string name="server_error_connection_generic">Network error connecting to %1$s. Check your connection and try again.
</string>
<string name="server_error_tls">Could not make a secure connection to %1$s. Hammer only connects over HTTPS, so the server needs a valid TLS certificate of its own or a reverse proxy that provides one.</string>
<string name="server_error_network_unavailable">Hammer could not open a network connection on this device, so the request never reached the server. Security software, a VPN, or a firewall may be blocking it.</string>
</resources>

View file

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_window_title">Hammer - Project Selection</string>
<string name="project_window_title">Hammer - %1$s</string>
<string name="project_window_menu_file">File</string>
<string name="project_window_menu_item_close">Close Project</string>
<string name="project_window_menu_item_exit">Exit</string>
<string name="jump_list_recent_projects">Recent Projects</string>
<!-- Sandbox first-run projects-directory flow (Mac App Store builds) -->
<string name="sandbox_intro_title">Welcome to Hammer</string>
<string name="sandbox_intro_message">Hammer stores your projects in a folder you choose, so your writing stays in
your hands.\n\nOn the next screen, pick an existing hammer projects folder or create a new one. You can change
this later in Settings.
</string>
<string name="sandbox_picker_title">Choose where to store your Hammer projects</string>
<string name="sandbox_choose_folder_button">Choose Folder</string>
<string name="sandbox_quit_button">Quit</string>
<string name="sandbox_retry_message">Hammer needs a folder to store your projects.\nChoose a folder now, or quit.
</string>
</resources>

View file

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="draft_compare_current_header">Merge with Current</string>
<string name="draft_compare_current_subheader">Copy parts of the draft into here</string>
<string name="draft_compare_current_accept_button">Take Merged</string>
<string name="draft_compare_draft_header">Draft: %1$s</string>
<string name="draft_compare_draft_subheader">Copy desired parts into current, or accept all of this.</string>
<string name="draft_compare_draft_accept_button">Take this whole draft</string>
<string name="draft_compare_tab_title_draft">Draft</string>
<string name="draft_compare_tab_title_current">Current</string>
<string name="draft_list_header">%1$s Drafts:</string>
<string name="draft_list_empty">No Drafts Found</string>
<string name="scene_draft_invalid_name">Invalid name</string>
</resources>

View file

@ -1,85 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="encyclopedia_header">Encyclopedia</string>
<string name="encyclopedia_search_hint">Search</string>
<string name="encyclopedia_search_clear_button">Clear</string>
<string name="encyclopedia_category_person">Person</string>
<string name="encyclopedia_category_place">Place</string>
<string name="encyclopedia_category_thing">Thing</string>
<string name="encyclopedia_category_event">Event</string>
<string name="encyclopedia_category_idea">Idea</string>
<string name="encyclopedia_browse_list_empty">No Entries Found</string>
<string name="encyclopedia_create_button">Create Entry</string>
<string name="encyclopedia_create_entry_header">Create entry</string>
<string name="encyclopedia_create_entry_section_marker">New</string>
<string name="encyclopedia_create_entry_draft_marker">Draft</string>
<string name="encyclopedia_create_entry_name_label">Name</string>
<string name="encyclopedia_create_entry_name_placeholder">e.g. Cheshire Cat</string>
<string name="encyclopedia_create_entry_tags_label">Tags</string>
<string name="encyclopedia_create_entry_tags_hint">↵ to add</string>
<string name="encyclopedia_create_entry_tags_placeholder">ritual, magical, recurring…</string>
<string name="encyclopedia_create_entry_description_label">Description</string>
<string name="encyclopedia_create_entry_cover_art_label">Cover Art</string>
<string name="encyclopedia_create_entry_image_drop_hint">Drop image · or</string>
<string name="encyclopedia_create_entry_image_browse_button">Browse files</string>
<string name="encyclopedia_create_entry_image_attached">1 attached</string>
<string name="encyclopedia_create_entry_image_replace">Replace</string>
<string name="encyclopedia_create_entry_image_too_large">Image is too large. Maximum size is %1$d MB.</string>
<string name="encyclopedia_create_entry_image_load_failed">Could not read the selected image.</string>
<string name="encyclopedia_create_entry_create_button">Create entry</string>
<string name="encyclopedia_create_entry_cancel_button">Cancel</string>
<string name="encyclopedia_create_entry_toast_success">Entry Created</string>
<string name="encyclopedia_create_entry_toast_too_long">Entry Name was too long. Max %1$d</string>
<string name="encyclopedia_create_entry_toast_invalid_name">The entry name contains invalid characters</string>
<string name="encyclopedia_create_entry_toast_tag_too_long">Tag is too long. Max %1$d</string>
<string name="encyclopedia_create_entry_toast_alias_too_long">Alias is too long. Max %1$d</string>
<string name="encyclopedia_entry_aliases_label">Aliases</string>
<string name="encyclopedia_entry_dictionary_toggle_label">Learn in spell check</string>
<string name="encyclopedia_entry_dictionary_toggle_hint">This entry&#8217;s name and aliases won&#8217;t be marked as misspelled</string>
<string name="encyclopedia_entry_add_alias_dialog_title">Add Alias</string>
<string name="encyclopedia_entry_add_alias_button">Add</string>
<string name="encyclopedia_entry_alias_hint">Nickname or other name</string>
<string name="encyclopedia_entry_appears_in_label">Appears in scenes</string>
<string name="encyclopedia_entry_appears_in_empty">Not yet referenced in any scene</string>
<string name="encyclopedia_create_entry_toast_name_too_short">Entry name cannot be empty</string>
<string name="encyclopedia_create_entry_discard_title">Discard New Entry?</string>
<string name="encyclopedia_entry_load_error">Error: Failed to load entry!</string>
<string name="encyclopedia_entry_body_empty_label">Click to enter description</string>
<string name="encyclopedia_entry_edit_cancel_button">Cancel</string>
<string name="encyclopedia_entry_edit_save_button">Save</string>
<string name="encyclopedia_entry_edit_save_toast">Entry Saved</string>
<string name="encyclopedia_entry_discard_title">Discard Changes?</string>
<string name="encyclopedia_entry_discard_message">You will lose any changes you have made.
</string>
<string name="encyclopedia_entry_delete_title">Delete Entry?</string>
<string name="encyclopedia_entry_delete_message">This cannot be undone!</string>
<string name="encyclopedia_entry_delete_toast">Entry Deleted</string>
<string name="encyclopedia_entry_delete_image_title">Delete Image?</string>
<string name="encyclopedia_entry_delete_image_message">This cannot be undone!</string>
<string name="encyclopedia_entry_name_hint">Name</string>
<string name="encyclopedia_entry_close_button">Close Entry</string>
<string name="encyclopedia_entry_menu_group">Entry</string>
<string name="encyclopedia_entry_menu_delete">Delete Entry</string>
<string name="encyclopedia_entry_menu_remove_image">Remove Image</string>
<string name="encyclopedia_entry_menu_add_image">Add Image</string>
<string name="encyclopedia_entry_add_tags_dialog_title">Add Tags</string>
<string name="encyclopedia_entry_add_tags_button">Add Tags</string>
<string name="encyclopedia_entry_crumb_root">Encyclopedia</string>
<string name="encyclopedia_entry_edit_button">Edit</string>
<string name="encyclopedia_entry_particulars_label">Particulars</string>
<string name="encyclopedia_entry_particulars_type">Type</string>
<string name="encyclopedia_entry_particulars_tags">Tags</string>
<string name="encyclopedia_entry_particulars_aliases">Aliases</string>
<string name="encyclopedia_entry_particulars_scenes">Scenes</string>
<string name="encyclopedia_entry_tags_label">Tags</string>
<string name="encyclopedia_entry_add_tag_chip">tag</string>
<string name="encyclopedia_entry_add_alias_chip">alias</string>
<string name="encyclopedia_entry_figure_caption">Fig. I · Entry</string>
<string name="encyclopedia_entry_figure_label">Fig. I</string>
<string name="encyclopedia_entry_figure_add">+ Add</string>
<string name="encyclopedia_entry_folio_format">Fol. %1$s · Ent-%2$s</string>
<string name="encyclopedia_entry_scene_index_format">Sc.%1$s</string>
<string name="encyclopedia_entry_colophon_label">Entry · Catalogue card</string>
<string name="encyclopedia_entry_colophon_label_compact">Edited</string>
<string name="encyclopedia_entry_colophon_end">End of entry ✦</string>
</resources>

View file

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="global_search_button">Search project</string>
<string name="global_search_title">Search project</string>
<string name="global_search_placeholder">Type to search, use #tag to filter…</string>
<string name="global_search_clear">Clear search</string>
<string name="global_search_no_results">No results</string>
<string name="global_search_too_short">Type at least 2 characters to search</string>
<string name="global_search_results_count">%1$d results</string>
<string name="global_search_source_scene">Scene</string>
<string name="global_search_source_note">Note</string>
<string name="global_search_source_encyclopedia">Encyclopedia</string>
<string name="global_search_source_timeline">Timeline</string>
<string name="global_search_filter_all">All</string>
<string name="global_search_filter_scenes">Scenes</string>
<string name="global_search_filter_notes">Notes</string>
<string name="global_search_filter_encyclopedia">Encyclopedia</string>
<string name="global_search_filter_timeline">Timeline</string>
</resources>

View file

@ -1,83 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="project_select_nav_story_ideas">Story Ideas</string>
<string name="project_select_nav_story_ideas_short">IDEA</string>
<string name="ideas_header">Story Ideas</string>
<string name="ideas_header_meta">%1$d IDEAS · %2$d TAGS</string>
<string name="ideas_list_empty">No story ideas yet. Capture your first spark.</string>
<string name="ideas_list_empty_archived">No archived ideas.</string>
<string name="ideas_word_count_short">%1$d W</string>
<string name="ideas_tag_count_short">%1$d TAGS</string>
<string name="ideas_search_placeholder">Search ideas</string>
<string name="ideas_search_button">Search ideas</string>
<string name="ideas_search_clear">Clear search</string>
<string name="ideas_search_close">Close search</string>
<string name="ideas_filter_all">All</string>
<string name="ideas_filter_archived">Archived</string>
<string name="ideas_filter_filtered">FILTERED %1$d / %2$d</string>
<string name="ideas_filter_clear_all">Clear All</string>
<string name="ideas_sort_label">SORT</string>
<string name="ideas_sort_newest">Newest</string>
<string name="ideas_sort_oldest">Oldest</string>
<string name="ideas_sort_title_az">Title A–Z</string>
<string name="ideas_sort_glyph_date_desc">↓ DATE</string>
<string name="ideas_sort_glyph_date_asc">↑ DATE</string>
<string name="ideas_sort_glyph_title_az">↓ A–Z</string>
<string name="ideas_stamp_promoted">PROMOTED · %1$s</string>
<string name="ideas_stamp_archived">ARCHIVED · %1$s</string>
<string name="ideas_create_fab">Capture a new idea</string>
<string name="ideas_create_marker">New Idea</string>
<string name="ideas_view_header">Story Idea</string>
<string name="ideas_view_label_editing">Editing</string>
<string name="ideas_view_status_unsaved">UNSAVED</string>
<string name="ideas_view_crumb_root">STORY IDEAS</string>
<string name="ideas_view_close_button">Close idea</string>
<string name="ideas_view_action_edit">Edit</string>
<string name="ideas_title_label">Title</string>
<string name="ideas_title_placeholder">Optional title</string>
<string name="ideas_tags_label">Tags</string>
<string name="ideas_tags_hint">Enter or comma adds a tag</string>
<string name="ideas_tags_placeholder">Add tag…</string>
<string name="ideas_editor_counter">%1$d / %2$d CH</string>
<string name="ideas_create_button">Create</string>
<string name="ideas_save_button">Save</string>
<string name="ideas_cancel_button">Cancel</string>
<string name="ideas_delete_button">Delete</string>
<string name="ideas_archive_button">Archive</string>
<string name="ideas_unarchive_button">Unarchive</string>
<string name="ideas_toast_created">Idea captured</string>
<string name="ideas_toast_saved">Idea saved</string>
<string name="ideas_toast_deleted">Idea deleted</string>
<string name="ideas_toast_archived">Idea archived</string>
<string name="ideas_toast_unarchived">Idea unarchived</string>
<string name="ideas_toast_empty">Idea cannot be empty</string>
<string name="ideas_toast_too_long">Idea is too long: %1$d characters max</string>
<string name="ideas_toast_tag_too_long">Tags can be at most %1$d characters</string>
<string name="ideas_promote_button">Create Project</string>
<string name="ideas_promote_dialog_title">Create Project from Idea</string>
<string name="ideas_promote_dialog_message">A new project will be created and this idea will be added to it as its first note.</string>
<string name="ideas_toast_promoted">Project created: %1$s</string>
<string name="ideas_toast_promote_failed">Failed to create project</string>
<string name="ideas_delete_dialog_title">Delete Idea</string>
<string name="ideas_delete_dialog_message">This idea will be permanently deleted. This cannot be undone.</string>
<string name="ideas_discard_dialog_title">Discard Changes</string>
<string name="ideas_discard_dialog_message">You have unsaved changes. Discard them?</string>
<string name="sync_log_ideas_begin">Syncing story ideas</string>
<string name="sync_log_ideas_unchanged">Story ideas unchanged — skipped</string>
<string name="sync_log_ideas_complete">Story ideas sync complete</string>
<string name="sync_log_ideas_state_failed">Failed to fetch story ideas state: %1$s</string>
<string name="sync_log_ideas_deleted_local">Removed deleted idea: %1$s</string>
<string name="sync_log_ideas_deleted_server">Propagated idea deletion to server: %1$s</string>
<string name="sync_log_ideas_delete_server_failed">Failed to delete idea on server: %1$s</string>
<string name="sync_log_ideas_uploaded">Uploaded idea: %1$s</string>
<string name="sync_log_ideas_upload_failed">Failed to upload idea "%1$s": %2$s</string>
<string name="sync_log_ideas_downloaded">Downloaded idea: %1$s</string>
<string name="sync_log_ideas_download_failed">Failed to download idea %1$s: %2$s</string>
<string name="sync_log_ideas_conflict">Conflict detected for idea: %1$s</string>
<string name="sync_log_ideas_conflict_resolved">Resolved conflict for idea: %1$s</string>
<string name="sync_log_ideas_conflict_skipped">Conflict left unresolved for idea: %1$s</string>
<string name="ideas_conflict_title">Story Idea Conflict</string>
<string name="ideas_conflict_field_title">Title</string>
<string name="ideas_conflict_field_tags">Tags</string>
<string name="ideas_conflict_field_content">Content</string>
</resources>

View file

@ -1,55 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="notes_header">Notes</string>
<string name="notes_list_empty">No Notes Found</string>
<string name="notes_create_note_button">Create Note</string>
<string name="notes_note_item_action_cancel">Cancel</string>
<string name="notes_discard_dialog_title">Discard Changes?</string>
<string name="notes_discard_dialog_message">You will lose any changes you have made.</string>
<string name="notes_create_header">Create Note</string>
<string name="notes_create_marker">III · NEW</string>
<string name="notes_create_draft_label">DRAFT</string>
<string name="notes_create_create_button">Create</string>
<string name="notes_create_cancel_button">Cancel</string>
<string name="notes_create_toast_success">Note Created</string>
<string name="notes_create_toast_too_long">Note was too long</string>
<string name="notes_create_toast_empty">Note was empty</string>
<string name="notes_create_toast_tag_too_long">Tag was too long (max %1$d)</string>
<string name="notes_create_tags_label">Tags</string>
<string name="notes_create_tags_hint">↵ to add</string>
<string name="notes_create_tags_placeholder">idea, todo, follow-up…</string>
<string name="notes_delete_title">Delete Note?</string>
<string name="notes_delete_message">This action cannot be undone!</string>
<string name="notes_delete_toast_success">Note Deleted</string>
<string name="notes_menu_group">Note</string>
<string name="notes_menu_delete">Delete</string>
<string name="notes_view_header">Note</string>
<string name="notes_view_label_editing">Editing</string>
<string name="notes_view_close_button">Close Note</string>
<string name="notes_view_crumb_root">Notes</string>
<string name="notes_view_action_edit">Edit</string>
<string name="notes_view_action_save">Save</string>
<string name="notes_view_status_unsaved">UNSAVED</string>
<string name="notes_view_status_word_char">%1$d W · %2$d CH</string>
<string name="notes_view_status_hint">ESC TO CANCEL</string>
<string name="notes_search_button">Search notes</string>
<string name="notes_search_placeholder">Search notes</string>
<string name="notes_search_close">Close search</string>
<string name="notes_search_clear">Clear search</string>
<string name="notes_header_meta">%1$d NOTES · %2$d TAGS</string>
<string name="notes_word_count_short">%1$d W</string>
<string name="notes_filter_all">ALL</string>
<string name="notes_filter_filtered">FILTERED · %1$d OF %2$d</string>
<string name="notes_filter_clear_all">CLEAR ALL</string>
<string name="notes_sort_label">SORT</string>
<string name="notes_sort_newest">Newest</string>
<string name="notes_sort_oldest">Oldest</string>
<string name="notes_sort_longest">Longest</string>
<string name="notes_sort_shortest">Shortest</string>
<string name="notes_sort_title_az">Title A–Z</string>
<string name="notes_sort_glyph_date_desc">↓ DATE</string>
<string name="notes_sort_glyph_date_asc">↑ DATE</string>
<string name="notes_sort_glyph_words_desc">↓ WORDS</string>
<string name="notes_sort_glyph_words_asc">↑ WORDS</string>
<string name="notes_sort_glyph_title_az">↓ A–Z</string>
</resources>

View file

@ -1,127 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="project_home_stat_created">Created: %1$s</string>
<string name="project_home_loading_stats">Calculating statistics...</string>
<string name="project_home_refresh_stats_button">Refresh statistics</string>
<string name="project_home_stats_dirty_indicator">Statistics may be out of date</string>
<string name="project_home_stat_num_scenes">Scenes</string>
<string name="project_home_stat_total_words">Total Words</string>
<string name="project_home_stat_encyclopedia_entries">Encyclopedia Entries</string>
<string name="project_home_stat_chapter_words">Words in Chapters</string>
<string name="project_home_stat_chapter_words_x_axis">Chapter</string>
<string name="project_home_stat_chapter_words_tooltip">%1$s: %2$s words</string>
<string name="project_home_stat_avg_words_per_scene">Avg Words / Scene</string>
<string name="project_home_stat_num_notes">Notes</string>
<string name="project_home_stat_num_timeline_events">Timeline Events</string>
<string name="project_home_stat_longest_scene">Longest Scene</string>
<string name="project_home_stat_longest_scene_words">%1$s words</string>
<string name="project_home_stat_longest_scene_empty">No scenes yet</string>
<string name="project_home_stat_last_edited_scene">Last Edited</string>
<string name="project_home_stat_scene_median">Median</string>
<string name="project_home_stat_reading_time">≈ %1$d min reading</string>
<string name="project_home_stat_pages">%1$d pages</string>
<string name="project_home_stat_daily_goal">Daily goal</string>
<string name="project_home_stat_weekly_goal">Weekly goal</string>
<string name="project_home_stat_this_week">This week</string>
<string name="project_home_stat_week_change_flat">no change vs last week</string>
<string name="project_home_stat_week_change_new">no data last week</string>
<string name="project_home_stat_today">Today</string>
<string name="project_home_stat_daily_avg">Daily avg</string>
<string name="project_home_stat_streak">Streak</string>
<string name="project_home_stat_streak_days">%1$d days</string>
<string name="project_home_stat_longest_streak">Longest: %1$d</string>
<string name="project_home_stat_days_written">Days written</string>
<string name="project_home_stat_best_day">Best day</string>
<string name="project_home_stat_best_day_value">%1$s · %2$s</string>
<string name="project_home_stat_characters_appearances">Characters by Appearances</string>
<string name="project_home_stat_connections">%1$d connections</string>
<string name="project_home_stat_words_per_device">Words by Device</string>
<string name="project_home_stat_activity">Activity · %1$d weeks</string>
<string name="project_home_stat_activity_tooltip">%1$s · %2$s words</string>
<string name="project_home_stat_activity_tooltip_empty">%1$s · no writing</string>
<string name="project_home_stat_avg_weekday">Avg weekday</string>
<string name="project_home_stat_avg_weekend">Avg weekend</string>
<string name="project_home_stat_chapter_words_summary">σ %1$d · min %2$d · max %3$d</string>
<string name="project_home_stat_donut_entries">entries</string>
<string name="project_home_stat_donut_tooltip">%1$s · %2$s entries</string>
<string name="project_home_stat_themes_title">Themes</string>
<string name="project_home_stat_themes_summary">%1$d tags · %2$d uses</string>
<string name="project_home_stat_themes_breadth">%1$d/4</string>
<string name="project_home_stat_themes_most_connective">Most connective</string>
<string name="project_home_stat_themes_all_four">Appears in all four types · %1$d uses</string>
<string name="project_home_stat_themes_breadth_of_four">Appears in %1$d of 4 types · %2$d uses</string>
<string name="project_home_stat_themes_all_four_short">All 4 types</string>
<string name="project_home_stat_themes_breadth_of_four_short">%1$d of 4 types</string>
<string name="project_home_stat_themes_distribution">Distribution</string>
<string name="project_home_stat_themes_showing">Showing %1$d of %2$d</string>
<string name="project_home_stat_themes_columns_legend">Rank · Tag · Breadth · Uses</string>
<string name="project_home_stat_themes_source_scenes">Scenes</string>
<string name="project_home_stat_themes_source_notes">Notes</string>
<string name="project_home_stat_themes_source_encyclopedia">Encyclopedia</string>
<string name="project_home_stat_themes_source_events">Events</string>
<string name="project_home_action_export">Export Story</string>
<string name="project_home_action_sync">Sync Story</string>
<string name="project_home_action_backup">Backup</string>
<string name="project_home_action_backup_toast_success">Backup Created: %1$s</string>
<string name="project_home_action_backup_toast_failure">Backup Failed</string>
<string name="project_home_action_export_toast_success">Story Exported</string>
<string name="project_home_action_export_toast_failure">Export failed</string>
<string name="project_home_export_dialog_title">Export Story</string>
<string name="project_home_export_section">EXPORT</string>
<string name="project_home_export_close">× CLOSE</string>
<string name="project_home_export_chapters_label">Treat top-level scenes as chapters</string>
<string name="project_home_export_format_label">Format</string>
<string name="project_home_export_format_markdown">Markdown (.md)</string>
<string name="project_home_export_format_epub">EPub (.epub)</string>
<string name="project_home_export_format_pdf">PDF (.pdf)</string>
<string name="project_home_export_format_docx">Word (.docx)</string>
<string name="project_home_export_format_rtf">RTF (.rtf)</string>
<string name="project_home_export_cancel">Cancel</string>
<string name="project_home_export_execute">Export</string>
<string name="project_home_export_number_chapters_label">Number chapter titles</string>
<string name="project_home_export_font_label">Font</string>
<string name="project_home_export_blank_lines_label">Keep blank lines between paragraphs</string>
<string name="project_home_export_blank_lines_hint">Turn off if you separate paragraphs with a blank line and do not want empty paragraphs in the export. Scene breaks and runs of two or more blank lines are kept.</string>
<string name="project_home_export_limit_scenes_label">Limit to specific scenes</string>
<string name="project_home_export_limit_scenes_hint">Only the selected scenes will be included in the export</string>
<string name="project_home_export_scenes_label">Scenes</string>
<string name="project_home_export_scenes_selected">%1$d of %2$d selected</string>
<string name="project_home_export_scenes_select_all">Select all</string>
<string name="project_home_export_scenes_clear_all">Clear all</string>
<string name="project_home_export_help_icon_description">Learn about export formats</string>
<string name="project_home_export_help_title">Choosing an Export Format</string>
<string name="project_home_export_help_intro">Each format suits a different purpose. Pick the one that matches how you plan to use the exported story.</string>
<string name="project_home_export_help_format_markdown">Plain text with light formatting; ideal for version control, re-importing, or other writing tools.</string>
<string name="project_home_export_help_format_epub">The standard e-book format; best for beta readers reading on e-readers and phones with reflowable text.</string>
<string name="project_home_export_help_format_pdf">A fixed page layout that looks the same everywhere; best for printing or sharing a final, read-only copy.</string>
<string name="project_home_export_help_format_docx">Opens in Word and Google Docs; best for editors and beta readers who leave comments or track changes.</string>
<string name="project_home_export_help_format_rtf">A broadly compatible rich-text format; a safe choice when you are unsure which word processor the recipient uses.</string>
<string name="project_home_export_help_dismiss">Got it</string>
<string name="project_home_export_contents_title">Contents</string>
<string name="project_home_export_byline">by %1$s</string>
<string name="project_home_action_import_toast_success">Story Imported</string>
<string name="project_home_action_import_toast_failure">Import failed</string>
<string name="project_home_import_dialog_title">Import Story</string>
<string name="project_home_import_heading_label">Chapter detection</string>
<string name="project_home_import_heading_auto">Auto</string>
<string name="project_home_import_heading_h1">H1 (#)</string>
<string name="project_home_import_heading_h2">H2 (##)</string>
<string name="project_home_import_split_label">Split into scenes</string>
<string name="project_home_import_split_formatting">Formatting</string>
<string name="project_home_import_split_pattern">Pattern</string>
<string name="project_home_import_split_single">Single scene</string>
<string name="project_home_import_pattern_label">Chapter pattern (regex)</string>
<string name="project_home_import_create_groups_label">Create chapter groups</string>
<string name="project_home_import_preview_label">Preview</string>
<string name="project_home_import_preview_empty">Nothing to import</string>
<string name="project_home_import_preview_reading">Reading your manuscript</string>
<string name="project_home_import_preview_count">Will create %1$d scene(s)</string>
<string name="project_home_import_large_scene_label">Large scene</string>
<string name="project_home_import_large_scene_one">One scene holds %1$s words, so chapter detection probably missed your chapter titles. Try another detection setting.</string>
<string name="project_home_import_large_scene_many">%1$d scenes hold %2$s words or more, so chapter detection probably missed some chapter titles. Try another detection setting.</string>
<string name="project_home_import_scene_word_count">%1$s W</string>
<string name="project_home_import_cancel">Cancel</string>
<string name="project_home_import_execute">Import</string>
<string name="project_home_action_settings_button">Settings</string>
<string name="project_home_menu_button">Project menu</string>
</resources>

View file

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="project_nav_home">Home</string>
<string name="project_nav_scene_editor">Story</string>
<string name="project_nav_notes">Notes</string>
<string name="project_nav_encyclopedia">Encyclopedia</string>
<string name="project_nav_time_line">Timeline</string>
<string name="project_nav_home_short">HOME</string>
<string name="project_nav_scene_editor_short">STRY</string>
<string name="project_nav_notes_short">NOTE</string>
<string name="project_nav_encyclopedia_short">ENCY</string>
<string name="project_nav_time_line_short">TIME</string>
<string name="nav_rail_expand">Expand navigation</string>
<string name="nav_rail_collapse">Collapse navigation</string>
</resources>

View file

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="project_select_nav_projects_list">Projects</string>
<string name="project_select_nav_account_settings">Settings</string>
<string name="project_select_nav_about_app">About</string>
<string name="project_select_nav_projects_list_short">PROJ</string>
<string name="project_select_nav_account_settings_short">SETT</string>
<string name="project_select_nav_about_app_short">INFO</string>
<string name="project_libraries_dialog_title">Libraries</string>
</resources>

View file

@ -1,81 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="project_select_project_list_empty">No Projects Found</string>
<string name="project_select_card_delete_button">Delete</string>
<string name="project_select_card_rename_button">Rename</string>
<string name="projects_list_sync_button">Sync Projects</string>
<string name="projects_list_create_button">Create Project</string>
<string name="projects_list_toast_sync_complete">Projects synced</string>
<string name="projects_list_toast_sync_failed">Failed to sync projects</string>
<string name="projects_list_toast_delete_failed">Could not delete the project. Check that its folder is accessible and try again.</string>
<string name="projects_list_item_more_button">More</string>
<string name="projects_list_card_words">%1$s words</string>
<string name="projects_list_card_by_author">by %1$s</string>
<string name="projects_list_card_created">Created %1$s</string>
<string name="projects_list_card_content_description">Project %1$s</string>
<string name="projects_list_search_button">Search projects</string>
<string name="projects_list_search_close">Close search</string>
<string name="projects_list_search_placeholder">filter by name or #tag</string>
<string name="projects_list_search_clear">Clear filter</string>
<string name="projects_list_no_matches">No projects match your filter</string>
<string name="projects_list_masthead_title">Hammer · Library</string>
<string name="projects_list_column_title">Title</string>
<string name="projects_list_column_last_open">Last Open</string>
<string name="projects_list_empty_index">— Empty Index —</string>
<string name="projects_list_footer_library">Library</string>
<string name="projects_list_footer_sync_on">Sync · On</string>
<string name="projects_list_footer_local">Local</string>
<string name="projects_list_entry_count_none">0 entries</string>
<string name="projects_list_entry_count_one">1 entry</string>
<string name="projects_list_entry_count_other">%1$d entries</string>
<string name="projects_list_sort_label">SORT</string>
<string name="projects_list_sort_recently_opened">Recently Opened</string>
<string name="projects_list_sort_least_recent">Least Recent</string>
<string name="projects_list_sort_newest">Newest</string>
<string name="projects_list_sort_oldest">Oldest</string>
<string name="projects_list_sort_longest">Longest</string>
<string name="projects_list_sort_shortest">Shortest</string>
<string name="projects_list_sort_glyph_opened_desc">↓ OPENED</string>
<string name="projects_list_sort_glyph_opened_asc">↑ OPENED</string>
<string name="projects_list_sort_glyph_created_desc">↓ CREATED</string>
<string name="projects_list_sort_glyph_created_asc">↑ CREATED</string>
<string name="projects_list_sort_glyph_words_desc">↓ WORDS</string>
<string name="projects_list_sort_glyph_words_asc">↑ WORDS</string>
<string name="account_sync_dialog_header">Account Sync</string>
<string name="account_sync_log_title">Sync Log</string>
<string name="account_sync_log_filter_label">Project</string>
<string name="account_sync_log_filter_all">All</string>
<string name="account_sync_dialog_status_pending">Pending</string>
<string name="account_sync_dialog_status_error">Error</string>
<string name="account_sync_dialog_status_conflict">Conflict</string>
<string name="account_sync_dialog_status_complete">Sync Complete</string>
<string name="account_sync_dialog_status_canceled">Canceled</string>
<string name="delete_project_title">Delete Project</string>
<string name="delete_project_warning">This action cannot be undone. All project data will be permanently deleted.</string>
<string name="delete_project_confirm_hint">Type "%1$s" to confirm</string>
<string name="delete_project_confirm">Delete</string>
<string name="delete_project_cancel">Cancel</string>
<string name="rename_project_title">Rename Project</string>
<string name="rename_project_heading">New Project Name</string>
<string name="rename_project_button">Rename Project</string>
<string name="rename_project_cancel_button">Cancel</string>
<string name="create_project_title">Create Project</string>
<string name="create_project_marker">New</string>
<string name="create_project_meta">Project</string>
<string name="create_project_heading">New Project Name</string>
<string name="create_project_button">Create Project</string>
<string name="create_project_cancel_button">Cancel</string>
<string name="create_project_import_button">Import</string>
<string name="create_project_import_help_icon_description">Learn about importing a story</string>
<string name="create_project_import_help_title">Create from an Existing Story</string>
<string name="create_project_import_help_intro">Already have a manuscript? Import it to create a new project. Hammer names the project after your file and automatically splits the text into scenes.</string>
<string name="create_project_import_help_formats_header">Supported Formats</string>
<string name="create_project_import_help_format_markdown">Markdown (.md)</string>
<string name="create_project_import_help_format_rtf">Rich Text (.rtf)</string>
<string name="create_project_import_help_dismiss">Got it</string>
<string name="create_project_error_already_exists">Project already exists</string>
<string name="create_project_error_null_filename">Missing Project Name</string>
<string name="create_project_error_too_long">Project name too long</string>
<string name="create_project_error_invalid_characters">The project name contains invalid characters</string>
<string name="create_project_error_blank">The project name was left blank</string>
</resources>

Some files were not shown because too many files have changed in this diff Show more