mirror of
https://github.com/Darkrock-Studios/hammer-editor.git
synced 2026-10-19 09:34:23 +00:00
Compare commits
39 commits
fdroid-311
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59a788cddd | ||
|
|
1ba66394ab | ||
|
|
c896edf379 | ||
|
|
073921c22b | ||
|
|
78060dfe88 | ||
|
|
05b1608bd4 | ||
|
|
db2131cab5 | ||
|
|
daa86badef | ||
|
|
9013716d0d | ||
|
|
f7605f984d | ||
|
|
81424d94ec | ||
|
|
83f9497937 | ||
|
|
366e5b6a6f | ||
|
|
66e9920306 | ||
|
|
beab5e4271 | ||
|
|
a90b72d291 | ||
|
|
1dfc2bf663 | ||
|
|
ff622e82d8 | ||
|
|
28b4df9b4a | ||
|
|
645225e3ac | ||
|
|
de23cbec43 | ||
|
|
385a6f28d2 | ||
|
|
2b4b1698b3 | ||
|
|
4f2004431f | ||
|
|
56e6e82b82 | ||
|
|
215fbf6537 | ||
|
|
2ad0322067 | ||
|
|
ba28b01b82 | ||
|
|
d884591bf6 | ||
|
|
ca22ec262d | ||
|
|
5d363ee7c4 | ||
|
|
4edff36e95 | ||
|
|
569b6c0297 | ||
|
|
1cdcdad657 | ||
|
|
13b078f26c | ||
|
|
a39b1e7fd4 | ||
|
|
3f038d836f | ||
|
|
1a433d78f9 | ||
|
|
04de488fb0 |
351 changed files with 11913 additions and 1464 deletions
91
.github/workflows/prepare-release.yml
vendored
91
.github/workflows/prepare-release.yml
vendored
|
|
@ -72,7 +72,7 @@ jobs:
|
|||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
shell: pwsh
|
||||
command: ./gradlew :desktop:packageReleaseMsi -Pchannel=github
|
||||
command: ./gradlew :desktop:packageReleaseMsi -Pchannel=github -Pformat=msi
|
||||
|
||||
- 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
|
||||
command: ./gradlew :desktop:packageReleaseExe -Pchannel=github -Pformat=exe
|
||||
|
||||
- name: Rename EXE Artifact
|
||||
shell: cmd
|
||||
|
|
@ -130,6 +130,17 @@ 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:
|
||||
|
|
@ -192,7 +203,7 @@ jobs:
|
|||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: ./gradlew :desktop:packageReleaseDeb -Pchannel=github
|
||||
command: ./gradlew :desktop:packageReleaseDeb -Pchannel=github -Pformat=deb
|
||||
|
||||
- name: Rename DEB Artifact
|
||||
run: |
|
||||
|
|
@ -216,7 +227,7 @@ jobs:
|
|||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: ./gradlew :desktop:packageReleaseRpm -Pchannel=github
|
||||
command: ./gradlew :desktop:packageReleaseRpm -Pchannel=github -Pformat=rpm
|
||||
|
||||
- name: Rename RPM Artifact
|
||||
run: |
|
||||
|
|
@ -240,7 +251,17 @@ jobs:
|
|||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: ./gradlew buildDistAppImage -Pchannel=appimage
|
||||
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
|
||||
|
||||
- name: Create Release
|
||||
uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0
|
||||
|
|
@ -283,7 +304,7 @@ jobs:
|
|||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: ./gradlew :desktop:packageReleaseDmg -Pchannel=github
|
||||
command: ./gradlew :desktop:packageReleaseDmg -Pchannel=github -Pformat=dmg
|
||||
|
||||
- name: Rename DMG Artifact
|
||||
run: |
|
||||
|
|
@ -307,7 +328,7 @@ jobs:
|
|||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
command: ./gradlew :desktop:packageReleasePkg -Pchannel=github
|
||||
command: ./gradlew :desktop:packageReleasePkg -Pchannel=github -Pformat=pkg
|
||||
|
||||
- name: Rename PKG Artifact
|
||||
run: |
|
||||
|
|
@ -328,6 +349,16 @@ 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:
|
||||
|
|
@ -465,6 +496,9 @@ 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:
|
||||
|
|
@ -474,7 +508,7 @@ jobs:
|
|||
prerelease: true
|
||||
artifactErrorsFailBuild: true
|
||||
allowUpdates: true
|
||||
artifacts: "${{ github.workspace }}/android/build/outputs/apk/release/hammer.apk"
|
||||
artifacts: "${{ github.workspace }}/android/build/outputs/apk/release/hammer.apk,${{ github.workspace }}/android/build/outputs/apk/release/hammer-apk-mapping.txt"
|
||||
|
||||
snap:
|
||||
needs: tests
|
||||
|
|
@ -591,6 +625,47 @@ 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/}
|
||||
|
|
|
|||
12
.github/workflows/publish-google-play.yml
vendored
12
.github/workflows/publish-google-play.yml
vendored
|
|
@ -89,3 +89,15 @@ 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
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ jobs:
|
|||
cache: gradle
|
||||
|
||||
- name: Build MSIX package
|
||||
run: ./gradlew :desktop:packageMsix -Pchannel=ms-store
|
||||
run: ./gradlew :desktop:packageMsix -Pchannel=ms-store -Pformat=msix
|
||||
|
||||
- name: Upload Gradle problems report on failure
|
||||
if: failure()
|
||||
|
|
|
|||
2
.github/workflows/publish-release.yml
vendored
2
.github/workflows/publish-release.yml
vendored
|
|
@ -11,6 +11,8 @@ 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
|
||||
|
|
|
|||
34
CHANGELOG.md
34
CHANGELOG.md
|
|
@ -1,5 +1,39 @@
|
|||
# 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
|
||||
|
|
|
|||
|
|
@ -174,6 +174,19 @@ 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
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ dependencies {
|
|||
implementation(libs.work.runtime.ktx)
|
||||
implementation(libs.material)
|
||||
implementation(libs.appcompat)
|
||||
implementation(libs.core.splashscreen)
|
||||
implementation(libs.multiplatform.settings)
|
||||
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ class WearPairingActivity : ComponentActivity(), KoinComponent {
|
|||
setContent {
|
||||
val isDark = when (settings.uiTheme) {
|
||||
UiTheme.Light -> false
|
||||
UiTheme.Dark -> true
|
||||
UiTheme.Dark, UiTheme.OledBlack -> true
|
||||
UiTheme.FollowSystem -> isSystemInDarkTheme()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
|
||||
<activity
|
||||
android:name=".ProjectSelectActivity"
|
||||
android:theme="@style/Theme.Hammer.Starting"
|
||||
android:exported="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ class ProjectRootActivity : AppCompatActivity() {
|
|||
val settingsState by globalSettings.subscribeAsState()
|
||||
val isDark = when (settingsState.uiTheme) {
|
||||
UiTheme.Light -> false
|
||||
UiTheme.Dark -> true
|
||||
UiTheme.Dark, UiTheme.OledBlack -> true
|
||||
UiTheme.FollowSystem -> isSystemInDarkTheme()
|
||||
}
|
||||
|
||||
|
|
@ -220,7 +220,8 @@ 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
|
||||
BackHandler(enabled = backEnabled && (component.hasUnsavedBuffers() || shouldConfirmClose.isNotEmpty())) {
|
||||
val unsavedBuffers by component.unsavedBuffers.subscribeAsState()
|
||||
BackHandler(enabled = backEnabled && (unsavedBuffers || shouldConfirmClose.isNotEmpty())) {
|
||||
component.requestClose()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ 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
|
||||
|
|
@ -43,6 +44,7 @@ class ProjectSelectActivity : AppCompatActivity() {
|
|||
private var settingsUpdateJob: Job? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
installSplashScreen()
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
|
||||
|
|
@ -61,7 +63,7 @@ class ProjectSelectActivity : AppCompatActivity() {
|
|||
val settingsState by globalSettings.subscribeAsState()
|
||||
val isDark = when (settingsState.uiTheme) {
|
||||
UiTheme.Light -> false
|
||||
UiTheme.Dark -> true
|
||||
UiTheme.Dark, UiTheme.OledBlack -> true
|
||||
UiTheme.FollowSystem -> isSystemInDarkTheme()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ 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
|
||||
|
|
@ -95,6 +96,8 @@ 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) {}
|
||||
|
|
@ -102,6 +105,8 @@ val component = object : ProjectSelection {
|
|||
override fun openLatestRelease() {}
|
||||
override fun dismissChangelog() {}
|
||||
override fun showChangelog() {}
|
||||
override fun updateApp() {}
|
||||
override fun dismissUpdate() {}
|
||||
override val backHandler = dummyBackHandler
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ class AddNoteActivity : ComponentActivity(), KoinComponent {
|
|||
val settingsState by globalSettings.subscribeAsState()
|
||||
val isDark = when (settingsState.uiTheme) {
|
||||
UiTheme.Light -> false
|
||||
UiTheme.Dark -> true
|
||||
UiTheme.Dark, UiTheme.OledBlack -> true
|
||||
UiTheme.FollowSystem -> isSystemInDarkTheme()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ class AddNoteWidgetConfigActivity : ComponentActivity() {
|
|||
val settingsState by globalSettings.subscribeAsState()
|
||||
val isDark = when (settingsState.uiTheme) {
|
||||
UiTheme.Light -> false
|
||||
UiTheme.Dark -> true
|
||||
UiTheme.Dark, UiTheme.OledBlack -> true
|
||||
UiTheme.FollowSystem -> isSystemInDarkTheme()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ class StoryInfoWidgetConfigActivity : ComponentActivity() {
|
|||
val settingsState by globalSettings.subscribeAsState()
|
||||
val isDark = when (settingsState.uiTheme) {
|
||||
UiTheme.Light -> false
|
||||
UiTheme.Dark -> true
|
||||
UiTheme.Dark, UiTheme.OledBlack -> true
|
||||
UiTheme.FollowSystem -> isSystemInDarkTheme()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
<?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>
|
||||
|
|
@ -46,5 +46,3 @@
|
|||
<string name="story_info_widget_strip_words_unit">P</string>
|
||||
<string name="story_info_widget_continue_label">CONTINUA ·</string>
|
||||
</resources>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
<?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>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<?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>
|
||||
48
android/src/main/res/values-tr/strings_android.xml
Normal file
48
android/src/main/res/values-tr/strings_android.xml
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<?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>
|
||||
|
|
@ -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">Refuser</string>
|
||||
<string name="wear_pairing_close">Fermer</string>
|
||||
<string name="wear_pairing_decline">Decline</string>
|
||||
<string name="wear_pairing_close">Close</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>
|
||||
|
|
@ -2,4 +2,10 @@
|
|||
<resources>
|
||||
<style name="Theme.Hammer" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -3,4 +3,5 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -4,10 +4,12 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import com.darkrockstudios.build.resolveDistributionChannel
|
||||
import com.darkrockstudios.build.distributionChannel
|
||||
import com.darkrockstudios.build.packageFormat
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
plugins {
|
||||
|
|
@ -78,12 +79,8 @@ buildConfig {
|
|||
buildConfigField("String", "APP_VERSION", "\"${libs.versions.app.get()}\"")
|
||||
buildConfigField("String", "DATA_VERSION", "\"${libs.versions.data.version.get()}\"")
|
||||
|
||||
// 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}\"")
|
||||
buildConfigField("String", "CHANNEL", "\"${project.distributionChannel().token}\"")
|
||||
buildConfigField("String", "PACKAGE_FORMAT", "\"${project.packageFormat().token}\"")
|
||||
}
|
||||
|
||||
val GIT_TASK_NAME = "install-git-hooks"
|
||||
|
|
|
|||
|
|
@ -37,5 +37,9 @@ 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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -32,16 +32,18 @@ object ProseHtml {
|
|||
|
||||
/**
|
||||
* The generating providers [flavour] would use, with prose layout applied. Pass the result to
|
||||
* [HtmlGenerator].
|
||||
* [HtmlGenerator]. With [keepBlankLines] off, a lone blank line between paragraphs produces
|
||||
* nothing and a run of two or more produces a single break.
|
||||
*/
|
||||
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"),
|
||||
MarkdownElementTypes.BLOCK_QUOTE to ProseContainerProvider("blockquote"),
|
||||
MarkdownElementTypes.MARKDOWN_FILE to ProseContainerProvider("body", keepBlankLines),
|
||||
MarkdownElementTypes.BLOCK_QUOTE to ProseContainerProvider("blockquote", keepBlankLines),
|
||||
)
|
||||
|
||||
/**
|
||||
|
|
@ -100,7 +102,10 @@ private object ProseParagraphProvider : TrimmingInlineHolderProvider() {
|
|||
}
|
||||
|
||||
/** Emits a break for each blank line the author left between two paragraphs. */
|
||||
private class ProseContainerProvider(tag: String) : SimpleTagProvider(tag) {
|
||||
private class ProseContainerProvider(
|
||||
tag: String,
|
||||
private val keepBlankLines: Boolean,
|
||||
) : SimpleTagProvider(tag) {
|
||||
override fun processNode(
|
||||
visitor: HtmlGenerator.HtmlGeneratingVisitor,
|
||||
text: String,
|
||||
|
|
@ -119,7 +124,12 @@ private class ProseContainerProvider(tag: String) : SimpleTagProvider(tag) {
|
|||
else -> {
|
||||
val isParagraph = child.type == MarkdownElementTypes.PARAGRAPH
|
||||
if (afterParagraph && isParagraph) {
|
||||
val breaks = (newlines - 1).coerceIn(0, ProseHtml.MAX_CONSECUTIVE_BREAKS)
|
||||
val blanks = (newlines - 1).coerceIn(0, ProseHtml.MAX_CONSECUTIVE_BREAKS)
|
||||
val breaks = when {
|
||||
keepBlankLines -> blanks
|
||||
blanks >= 2 -> 1
|
||||
else -> 0
|
||||
}
|
||||
repeat(breaks) { visitor.consumeHtml(BREAK) }
|
||||
}
|
||||
child.accept(visitor)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ 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
|
||||
|
|
@ -128,6 +129,7 @@ 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.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
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
|
||||
|
|
@ -43,3 +45,13 @@ 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)
|
||||
}
|
||||
|
|
|
|||
45
buildSrc/src/main/java/packageFormat.kt
Normal file
45
buildSrc/src/main/java/packageFormat.kt
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
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())
|
||||
145
buildSrc/src/main/java/updateMetadata.kt
Normal file
145
buildSrc/src/main/java/updateMetadata.kt
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
34
buildSrc/src/test/java/PackageFormatTest.kt
Normal file
34
buildSrc/src/test/java/PackageFormatTest.kt
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
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)
|
||||
}
|
||||
}
|
||||
140
buildSrc/src/test/java/UpdateMetadataTest.kt
Normal file
140
buildSrc/src/test/java/UpdateMetadataTest.kt
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
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() }
|
||||
}
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
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()
|
||||
|
|
@ -9,31 +9,46 @@ 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(
|
||||
context: Context,
|
||||
class EncryptedSharedPrefsAuthTokenStore internal constructor(
|
||||
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 {
|
||||
val masterKey = MasterKey.Builder(context)
|
||||
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
||||
.build()
|
||||
try {
|
||||
openPrefs()
|
||||
} catch (e: GeneralSecurityException) {
|
||||
recreatePrefs(e)
|
||||
} catch (e: IOException) {
|
||||
recreatePrefs(e)
|
||||
}
|
||||
}
|
||||
|
||||
EncryptedSharedPreferences.create(
|
||||
context,
|
||||
PREFS_NAME,
|
||||
masterKey,
|
||||
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
||||
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
|
||||
)
|
||||
// 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()
|
||||
}
|
||||
|
||||
override fun get(url: String, userId: Long): AuthTokens? = lock.withLock {
|
||||
|
|
@ -54,11 +69,11 @@ class EncryptedSharedPrefsAuthTokenStore(
|
|||
}
|
||||
|
||||
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 parse encrypted auth tokens; treating as empty", e)
|
||||
Napier.w("Failed to read encrypted auth tokens; treating as empty", e)
|
||||
emptyMap()
|
||||
}
|
||||
}
|
||||
|
|
@ -70,5 +85,26 @@ class EncryptedSharedPrefsAuthTokenStore(
|
|||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
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
|
||||
|
|
@ -36,4 +39,5 @@ actual val platformModule = module {
|
|||
single<AndroidShareService>()
|
||||
single<BackupManagerService>()
|
||||
single<GlobalSettingsDatasource> { get<GlobalSettingsFilesystemDatasource>() }
|
||||
single<AppUpdater> { NoOpAppUpdater }
|
||||
}
|
||||
|
|
@ -1,17 +1,32 @@
|
|||
## [3.11.0] - 2026-9-24
|
||||
## [3.12.0] - 2026-10-5
|
||||
|
||||
New features
|
||||
- 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
|
||||
- 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
|
||||
- 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
|
||||
- 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
|
||||
- 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
|
||||
- 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")
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,17 @@
|
|||
<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…</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>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
<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>
|
||||
|
|
@ -55,6 +56,8 @@
|
|||
<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>
|
||||
|
|
@ -83,6 +86,7 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -78,6 +78,10 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
<?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>
|
||||
|
|
@ -24,6 +24,7 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@
|
|||
<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_overview_button">Gliederungsübersicht</string>
|
||||
<string name="scene_list_outline_button">Gliederung</string>
|
||||
<string name="scene_list_reader_button">Read</string>
|
||||
<string name="scene_list_outline_overview_none"><keine Gliederung></string>
|
||||
<string name="scene_list_outline_dismiss">Schließen</string>
|
||||
<string name="scene_list_outline_overview_eyebrow">Manuskript</string>
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,17 @@
|
|||
<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…</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>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
<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>
|
||||
|
|
@ -55,6 +56,8 @@
|
|||
<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>
|
||||
|
|
@ -83,6 +86,7 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -78,6 +78,10 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
<?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>
|
||||
|
|
@ -24,6 +24,7 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@
|
|||
<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_overview_button">Vista del esquema</string>
|
||||
<string name="scene_list_outline_button">Esquema</string>
|
||||
<string name="scene_list_reader_button">Read</string>
|
||||
<string name="scene_list_outline_overview_none"><sin esquema></string>
|
||||
<string name="scene_list_outline_dismiss">Descartar</string>
|
||||
<string name="scene_list_outline_overview_eyebrow">Manuscrito</string>
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,17 @@
|
|||
<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…</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>
|
||||
|
|
@ -21,9 +32,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">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>
|
||||
<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>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
<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>
|
||||
|
|
@ -36,10 +37,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">Plain HTTP: your email, password and writing travel unencrypted. Only use this on a network you trust.</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_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 characters</string>
|
||||
<string name="settings_server_setup_password_requirements">%1$d-%2$d personnages</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>
|
||||
|
|
@ -52,9 +53,11 @@
|
|||
<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">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="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="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>
|
||||
|
|
@ -71,8 +74,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">Learn encyclopedia names</string>
|
||||
<string name="settings_spellcheck_encyclopedia_enable_hint">Entry names and aliases won’t be marked as misspelled while their project is open</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_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>
|
||||
|
|
@ -83,6 +86,7 @@
|
|||
<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>
|
||||
|
|
@ -123,5 +127,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 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>
|
||||
<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>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -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">The entry name contains invalid characters</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_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>
|
||||
|
|
|
|||
|
|
@ -78,10 +78,14 @@
|
|||
<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_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_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_scenes_label">Scènes</string>
|
||||
<string name="project_home_export_scenes_selected">%1$d of %2$d selected</string>
|
||||
<string name="project_home_export_scenes_selected">%1$d sur %2$d sélectionnés</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>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
<?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>
|
||||
|
|
@ -24,7 +24,8 @@
|
|||
<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_add_to_dictionary">Add to dictionary</string>
|
||||
<string name="scene_editor_menu_item_read">Lire</string>
|
||||
<string name="scene_editor_add_to_dictionary">Ajouter au dictionnaire</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>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@
|
|||
<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_overview_button">Aperçu du plan</string>
|
||||
<string name="scene_list_outline_button">Plan</string>
|
||||
<string name="scene_list_reader_button">Lire</string>
|
||||
<string name="scene_list_outline_overview_none"><aucun plan></string>
|
||||
<string name="scene_list_outline_dismiss">Ignorer</string>
|
||||
<string name="scene_list_outline_overview_eyebrow">Manuscrit</string>
|
||||
|
|
|
|||
|
|
@ -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">Learn encyclopedia names in this project</string>
|
||||
<string name="project_settings_spellcheck_encyclopedia_enable_hint">Turn off to keep this project’s entry names out of the spell check dictionary</string>
|
||||
<string name="project_settings_dictionary_label">Project dictionary</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_dictionary_hint">↵ ajouter</string>
|
||||
<string name="project_settings_dictionary_placeholder">Words the spell checker should accept in this project</string>
|
||||
<string name="project_settings_dictionary_placeholder">Les mots que le correcteur orthographique devrait accepter dans ce projet</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">Learn Encyclopedia Names</string>
|
||||
<string name="sync_conflict_project_data_field_dictionary">Apprendre les noms de l'encyclopédie</string>
|
||||
<string name="sync_conflict_project_data_value_unset">(non défini)</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_value_on">Activer</string>
|
||||
<string name="sync_conflict_project_data_value_off">Désactiver</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">Project dictionary words from both sides are kept.</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_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">Project created on server, but failed to save its ID locally: %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_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">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_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_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">No changes, skipped</string>
|
||||
<string name="sync_log_project_unchanged">Pas de modification, ignorée</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,8 +193,7 @@
|
|||
<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">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_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_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>
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -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 is made by Dark Rock Studios, alongside a family of other privacy-respecting, open source apps.</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_header">Community</string>
|
||||
<string name="about_community_discord_link">Discord</string>
|
||||
<string name="about_community_reddit_link">Reddit</string>
|
||||
|
|
@ -10,9 +10,20 @@
|
|||
<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">Changes</string>
|
||||
<string name="about_version_github_button">GitHub Release</string>
|
||||
<string name="changelog_dialog_title">What's New</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…</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="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>
|
||||
|
|
@ -21,9 +32,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">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>
|
||||
<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>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
<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>
|
||||
|
|
@ -55,6 +56,8 @@
|
|||
<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>
|
||||
|
|
@ -83,6 +86,7 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -78,6 +78,10 @@
|
|||
<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>
|
||||
|
|
@ -115,7 +119,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 W</string>
|
||||
<string name="project_home_import_scene_word_count">%1$s P</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>
|
||||
|
|
|
|||
|
|
@ -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">NOTE</string>
|
||||
<string name="project_nav_notes_short">NOTA</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>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
<?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>
|
||||
|
|
@ -24,6 +24,7 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@
|
|||
<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_overview_button">Panoramica della scaletta</string>
|
||||
<string name="scene_list_outline_button">Scaletta</string>
|
||||
<string name="scene_list_reader_button">Read</string>
|
||||
<string name="scene_list_outline_overview_none"><nessuna scaletta></string>
|
||||
<string name="scene_list_outline_dismiss">Ignora</string>
|
||||
<string name="scene_list_outline_overview_eyebrow">Manoscritto</string>
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,17 @@
|
|||
<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…</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>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
<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>
|
||||
|
|
@ -55,6 +56,8 @@
|
|||
<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>
|
||||
|
|
@ -83,6 +86,7 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -78,6 +78,10 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
<?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>
|
||||
|
|
@ -24,6 +24,7 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@
|
|||
<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_overview_button">Visão geral</string>
|
||||
<string name="scene_list_outline_button">Esboço</string>
|
||||
<string name="scene_list_reader_button">Read</string>
|
||||
<string name="scene_list_outline_overview_none"><sem esboço></string>
|
||||
<string name="scene_list_outline_dismiss">Descartar</string>
|
||||
<string name="scene_list_outline_overview_eyebrow">Manuscrito</string>
|
||||
|
|
|
|||
41
common/src/commonMain/composeResources/values-tr/strings.xml
Normal file
41
common/src/commonMain/composeResources/values-tr/strings.xml
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?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>
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?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…</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>
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
<?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’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 & 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>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<?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>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<?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>
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
<?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’s name and aliases won’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>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?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>
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
<?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>
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
<?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>
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
<?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>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<?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>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?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>
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
<?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
Loading…
Add table
Add a link
Reference in a new issue